日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > python >内容正文

python

Python笔记三之闭包与装饰器

發(fā)布時(shí)間:2024/1/8 python 45 coder
生活随笔 收集整理的這篇文章主要介紹了 Python笔记三之闭包与装饰器 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文首發(fā)于公眾號(hào):Hunter后端

原文鏈接:Python筆記三之閉包與裝飾器

這一篇筆記介紹 Python 里面的裝飾器。

在介紹裝飾器前,首先提出這樣一個(gè)需求,我想統(tǒng)計(jì)某個(gè)函數(shù)的執(zhí)行時(shí)間,假設(shè)這個(gè)函數(shù)如下:

import time

def add(x, y):
    time.sleep(1)
    return x + y

想要統(tǒng)計(jì) add 函數(shù)的執(zhí)行時(shí)間,可以如何操作,在一般情況下,可能會(huì)想到如下操作:

start_time = time.time()
add(1, 2)
end_time = time.time()
print("函數(shù)執(zhí)行時(shí)間為:", end_time - start_time)

而如果我們想要統(tǒng)計(jì)很多個(gè)函數(shù)的執(zhí)行時(shí)間,然后打印出來(lái),應(yīng)該如何操作呢?

這里就可以用上 Python 里裝飾器的操作。

本篇筆記目錄如下:

  1. 閉包
    1. 閉包
    2. 閉包實(shí)現(xiàn)計(jì)數(shù)器
      1. *變量
  2. 裝飾器
  3. 裝飾器代碼示例裝飾器原理
  4. 裝飾器加參數(shù)
  5. 多重裝飾器
  6. 裝飾器類(lèi)

1、閉包

在介紹裝飾器前,先來(lái)理解一下閉包的概念。

1. 閉包

我們知道,一個(gè)函數(shù)內(nèi)部的變量是局部變量,在函數(shù)執(zhí)行結(jié)束之后,函數(shù)內(nèi)部的變量就會(huì)被銷(xiāo)毀,而閉包,則可以使我們能夠讀取函數(shù)內(nèi)部變量。

比如下面這個(gè)示例:

def outer_func():
    msg = "outer info"

    def inner_func():
        print(msg)
        return msg

    return inner_func


func = outer_func()
func()

關(guān)于閉包,2023.11.13 百度百科的釋義如下:

閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。例如在javascript中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以閉包可以理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)“。在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的橋梁。

所以閉包的作用可以是避免全局變量可能帶來(lái)的維護(hù)問(wèn)題,又能夠長(zhǎng)久的保存變量。

但是同時(shí),基于這個(gè)特性,閉包函數(shù)內(nèi)部的局部變量因?yàn)闀?huì)保持在內(nèi)存中,不會(huì)在調(diào)用后被自動(dòng)清除,所以需要注意其可能帶來(lái)的內(nèi)存泄漏的問(wèn)題。

2. 閉包實(shí)現(xiàn)計(jì)數(shù)器

下面我們使用閉包來(lái)實(shí)現(xiàn)一個(gè)計(jì)數(shù)器的功能:

def create_counter():
    count = 0

    def add_counter():
        nonlocal count
        count += 1
        return count
    return add_counter

f = create_counter()
print(f())
print(f())
print(f())

這里使用 nolocal 對(duì) count 變量進(jìn)行了聲明,作用是聲明該變量只在函數(shù)局部?jī)?nèi)起作用,也就是 create_counter() 內(nèi),所以在 add_counter() 外聲明 count 變量之后,在 add_counter() 內(nèi)可以保存其相應(yīng)的狀態(tài),也就是這里我們的計(jì)數(shù)功能。

nolocal 關(guān)鍵字是專門(mén)定義在閉包內(nèi)使用的。

相對(duì)應(yīng)的 global 字段時(shí)定義的全局變量,這里不多做介紹了。

*變量

*變量的含義是指未綁定到本地作用域的變量,比如上面的示例里,countadd_counter() 函數(shù)里就是一個(gè)*變量,因?yàn)樗谕鈱雍瘮?shù) create_counter() 里定義,但沒(méi)有在內(nèi)層的 add_counter() 中定義。

至于為什么在 add_counter() 里對(duì) count 變量進(jìn)行 nolocal 的聲明,是因?yàn)樾揎椀膶?duì)象類(lèi)型是 int,與之類(lèi)似的還有 strtuple,他們都屬于不可變類(lèi)型。

而如果我們閉包的內(nèi)外部函數(shù)里的對(duì)象是 list,dict 這種可變類(lèi)型,那么則不需要使用 nolocal 來(lái)進(jìn)行修飾,比如下面的操作:

def create_counter():
    count_dict = [0]??
    
    def add_counter():??????? 
        count_dict[0] += 1??????? 
        return count_dict[0]??? 
        
    return add_counter

2、 裝飾器

裝飾器的作用是在不修改被裝飾函數(shù)的情況下,給被裝飾的函數(shù)添加額外的功能。

而裝飾器就是基于閉包的操作,不過(guò)外層函數(shù)傳入的參數(shù)是被裝飾的函數(shù),且在 Python 里,使用裝飾器的方式是在被裝飾函數(shù)前加一行,使用 @ 符號(hào)來(lái)調(diào)用。

最簡(jiǎn)單的裝飾器的操作如下:

def decorator(func):
    print("calling decorator ...")
    return func


@decorator
def test():
    print("calling test ...")

我們?cè)谙旅娴牟僮髦惺褂靡粋€(gè)示例介紹如何基于閉包使用裝飾器。

3、裝飾器代碼示例

前面我們介紹了一個(gè)需求場(chǎng)景,需要統(tǒng)計(jì)函數(shù)的執(zhí)行時(shí)間,基于這個(gè)需求,我們就可以使用裝飾器的操作來(lái)完成,以下是代碼示例:

import time

def time_decorator(func):

    def inner_func(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        total_time = time.time() - start_time
        print("func 耗時(shí):", total_time)
        return result
    return inner_func


@time_decorator
def add(x, y):
    time.sleep(1)
    return x + y

add(1, 7)

裝飾器原理

我們使用 @ 加上裝飾器函數(shù)名稱,即表示調(diào)用這個(gè)裝飾器,然后將被裝飾的函數(shù),上面的示例是 add() 函數(shù),作為參數(shù)傳入裝飾器,然后在內(nèi)部函數(shù) inner_func() 中添加額外的功能,這里是統(tǒng)計(jì)函數(shù)運(yùn)行時(shí)間,然后將其返回。

將裝飾器的操作扁平化操作,就和前面閉包示例計(jì)數(shù)器的使用是一致的:

def add(x, y):
    time.sleep(1)
    return x + y

func = time_decorator(add)
func(1, 2)

所以,在加了裝飾器的函數(shù)運(yùn)行中,實(shí)際上運(yùn)行的是裝飾器的內(nèi)部函數(shù),我們可以通過(guò)打印函數(shù)的名稱來(lái)進(jìn)行驗(yàn)證:

print(add.__name__)  # inner_func

如果想要保存原函數(shù)的基本信息,比如函數(shù)名稱,我們可以給裝飾器的內(nèi)部函數(shù)加上裝飾器自動(dòng)復(fù)制函數(shù)信息,functools.wraps,使用示例如下:

import time
import functools

def time_decorator(func):

    @functools.wraps(func)
    def inner_func(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        total_time = time.time() - start_time
        print("func 耗時(shí):", total_time)
        return result
    return inner_func

@time_decorator
def add(x, y):
    time.sleep(1)
    return x + y
   
print(add.__name__)  # add

這樣打印的就是原始函數(shù)的函數(shù)名稱了。

4、裝飾器加參數(shù)

如果我們想調(diào)用裝飾器的時(shí)候,給裝飾器加一個(gè)參數(shù),比如這里的 time_decorator,想加一個(gè)默認(rèn)的時(shí)間參數(shù)(這個(gè)想要實(shí)現(xiàn)的功能可能并沒(méi)有實(shí)際意義,純粹是為了實(shí)現(xiàn)給裝飾器加默認(rèn)參數(shù)這個(gè)功能),調(diào)用的時(shí)候就是:

@time_decorator(default_time=2)

那么裝飾器的定義則如下所示:

def time_decorator(default_time=2):
    def decorator(func):
        def inner_func(*args, **kwargs):
            start_time = time.time()
            time.sleep(default_time)
            result = func(*args, **kwargs)
            total_time = time.time() - start_time
            print("func 耗時(shí):", total_time)
            return result
        return inner_func
    return decorator


@time_decorator(2)
def add(x, y):
    time.sleep(1)
    return x + y

add(1, 8)

如果調(diào)用裝飾器的時(shí)候想使用默認(rèn)參數(shù),直接不賦值即可:

@time_decorator()
def add(x, y):
    time.sleep(1)
    return x + y

5、多重裝飾器

如果我們想要調(diào)用多個(gè)裝飾器來(lái)裝飾一個(gè)函數(shù),其執(zhí)行順序是怎么要的呢,我們可以用下面的例子做個(gè)實(shí)驗(yàn)。

比如我們要做一個(gè)漢堡,最外層兩片面包,中間夾兩片青菜,最中間是一片肉,可以如下操作:

def bread_decorator(func):

    def inner(*args, **kwargs):
        print("先加片面包")
        func(*args, **kwargs)
        print("再加片面包")
    return inner


def vegetable_decorator(func):

    def inner(*args, **kwargs):
        print("先加片蔬菜")
        func(*args, **kwargs)
        print("再加片蔬菜")
    return inner


@bread_decorator
@vegetable_decorator
def make_hamburger():
    print("加片肉")


make_hamburger()

輸出的結(jié)果為:

先加片面包
先加片蔬菜
加片肉
再加片蔬菜
再加片面包

所以這里裝飾器的執(zhí)行時(shí)按照順序從上到下執(zhí)行的。

我們可以嘗試將裝飾器的調(diào)用拉平,用到的其實(shí)就是設(shè)計(jì)模式里的裝飾器模式了(設(shè)計(jì)模式的幾種類(lèi)型我回頭會(huì)更新一個(gè)系列),我們先將 make_hamburger() 的函數(shù)重新定義,然后調(diào)用,bread_decorator()vege_decorator() 還是保持不變:

def make_hamburger():??? 
    print("加片肉")

food = vegetable_decorator(make_hamburger)
food = bread_decorator(food)
food()

執(zhí)行的結(jié)果和前面使用裝飾器的方式調(diào)用是一致的。

6、裝飾器類(lèi)

前面介紹的是用函數(shù)作為裝飾器,我們還可以設(shè)計(jì)一個(gè)類(lèi)用作裝飾器,示例如下:

class TimeLogDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):

        start_time = time.time()
        result = self.func(*args, **kwargs)
        print(f"函數(shù) {self.func.__name__} 運(yùn)行時(shí)間為:{time.time() - start_time}")
        return result


@TimeLogDecorator
def add(x, y):
    time.sleep(1)
    return x + y


result = add(1, 6)

在類(lèi)的 __call__ 方法寫(xiě)入我們?cè)诤瘮?shù)裝飾器的內(nèi)部函數(shù)里的內(nèi)容即可實(shí)現(xiàn)裝飾器的功能。

如果想要給類(lèi)裝飾器帶參數(shù)的話,示例如下:

class TimeLogDecoratorArg:
    def __init__(self, base_gap_time):
        self.base_gap_time = base_gap_time

    def __call__(self, func):

        def inner_func(*args, **kwargs):
            start_time = time.time()
            time.sleep(self.base_gap_time)
            result = func(*args, **kwargs)
            print(f"函數(shù) {func.__name__} 運(yùn)行時(shí)間為:{time.time() - start_time}")
            return result
        return inner_func


@TimeLogDecoratorArg(2)
def add(x, y):
    time.sleep(1)
    return x + y

如果想獲取更多相關(guān)文章,可掃碼關(guān)注閱讀:

總結(jié)

以上是生活随笔為你收集整理的Python笔记三之闭包与装饰器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。