Python函數詳解二(閉包、裝飾器)

閉包

閉包其實利用了函數嵌套的概念,一般函數在內部定義一個變量,在外部由於作用域的關係是調用不到的,而閉包是將變量包起來,不管在哪裏都可以調用的到。

函數的嵌套定義:函數內定義了另外一個函數

# 示例1
name='Alice'
def outer():
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

其中outer稱爲外部函數,inner稱爲內部函數。

對於一個函數,outer是其函數名,outer()爲函數的調用,python中函數名可以用做函數的參數也可以作爲函數的返回值。

那麼什麼是閉包呢?

閉包滿足的三個條件:

1. 必須是嵌套函數;

2. 內部函數必須引用外部函數中的變量

3. 外部函數返回了內部函數或者外部函數調用了內部函數

查看是否爲閉包:__closure__

#示例2
def outer():
    name = 'Alice'
    def inner():
        print(name)
    print(inner.__closure__)    #(<cell at 0x000001B4AEF3A0D8: str object at 0x000001B4AEF190F0>,)
    return inner

可以試一下示例1的__closure__值,值是None,所以示例1並不是閉包,原因是內部函數沒有引用外部函數的變量。

內部函數的調用

f=outer()    #調用外部函數,返回內部函數並存給f
f()    #調用內部函數

注意:

內函數不能直接被調用。

閉包中被內部函數引用的變量,不會因爲外部函數結束而被釋放掉,而是一直存在內存中,直到內部函數被調用結束。

閉包變量

局部變量的作用域在函數內部,如果內部函數想修改外部函數的變量,有兩種方法:

1.將變量聲明爲nonlocal,這表明變量不僅在函數內部生效,還在上一級函數中生效

def outer():
    name = 'Alice'
    def inner():
        nonlocal name
        name='Haha'
        print(name)
    return inner


f=outer()
f()

2.把閉包變量改成可變類型數據進行修改,比如列表。

def outer():
    name = ['Alice']
    def inner():
        name=['Haha']
        print(name[0])
    return inner


f=outer()
f()

注意:

1.nonlocal關鍵字只能用於嵌套函數中,並且外部函數中定義了相應的局部變量

2.外函數被調用一次返回了內函數,因此實際上閉包變量只有一份,以後每次調用內函數,都使用同一份閉包變量,一旦在內部函數修改了閉包變量,則這個閉包變量的值就已經修改了,不是最初的值了。

裝飾器

裝飾器本質上是一個函數,使用了閉包的特性,它可以讓其他函數在不需要做任何代碼變動的前提下增加額外功能,裝飾器的返回值也是一個函數對象。

現有函數:

def sleep_f():
    time.sleep(3)

如果要計算該函數的運行時間,代碼如下

start_time=time.time()
sleep_f()
end_time=time.time()
print("程序運行了{0:.4f}秒".format(end_time-start_time))

缺點:

1.調用sleep_f()依舊無法打印運行時間。

2.如果還有其他很多函數要顯示運行時間,同樣的代碼得寫很多遍。

解決方法:使用裝飾器。

定義裝飾器如下:

def timer(func):
    def inner(*args, **kwargs):
        start_time=time.time()
        func(*args, **kwargs)
        end_time=time.time()
        print("程序運行了{0:.4f}秒".format(end_time-start_time))
    return inner

裝飾器裝飾函數的方法:函數的上一行加 @裝飾器名,其中,@符號是裝飾器的語法糖

裝飾器使用的兩種方法:

裝飾器不帶參數

# 被裝飾函數不帶參數
@timer
def sleep_f():
    time.sleep(3)
#調用sleep_f,這時候就會打印運行時間了
sleep_f()
# 被裝飾函數帶參數
@timer
def add(a,b):
    print(a+b)
    time.sleep(2)


add(2,3)

裝飾器帶參數

timer裝飾器打印了函數的運行時間,如果還想打印日誌,即想要裝飾器含參數,需要在timer的外層再裝飾一層函數

def flog(name):
    def timer(func):
        def inner(*args, **kwargs):
            print("調用{}模塊的{}函數".format(name,inner.__name__))
            start_time=time.time()
            func(*args, **kwargs)
            end_time=time.time()
            print("程序運行了{0:.4f}秒".format(end_time-start_time))
        return inner
    return timer
@flog('module_a')
def add(a,b):
    print(a+b)
    time.sleep(2)
# 調用
add(6,7)

推薦閱讀

Python函數詳解一(函數參數、變量作用域)

你點的每個好看,我都認真當成了喜歡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章