在一個外函數中定義了一個內函數,內函數裏運用了外函數的臨時變量,而且外函數的返回值是內函數的引用。這樣就構成了一個閉包。[1]html
如下給出一個閉包的例子:python
def outer():
a = 10
def inner():
b= 10
print(b)
print(a)
return inner
if __name__ == '__main__':
inner_func = outer()
inner_func()
>> 10
複製代碼
在這裏a做爲outer的局部變量,通常狀況下會在函數結束的時候釋放爲a分配到的內存。可是在閉包中,若是外函數在結束的時候發現有本身的臨時變量未來會在內部函數中用到,就把這個臨時變量綁定給了內部函數,而後本身再結束。[1]緩存
在inner中,a是一個自由變量(free variable). 這是一個技術術語,指未在本地做用域綁定的變量。[2]bash
在python中,__code__屬性中保存着局部變量的和自由變量的名稱,對於inner函數,其局部變量和自由變量爲:閉包
inner_func = outer()
inner_func.__code__.co_freevars
>> ('a',)
inner_func.__code__.co_varnames
>> ('b',)
複製代碼
那麼,既然外部函數會把內部變量要用到的變量(即內部函數的自由變量)綁定給內部函數,那麼a的綁定在哪裏?a的綁定在返回函數的inner的__closure__屬性中,其中的cell_contents保存着真正的值。[2]app
inner_func.__closure__[0].cell_contents
>> 10
複製代碼
綜上,閉包是一種函數,它會保留定義函數時存在的自由變量的綁定,這樣調用函數時,雖然定義做用域不可用了,可是仍能使用那些綁定。[2]函數
當咱們嘗試在inner改變a的值的時候性能
def outer():
a = 10
def inner():
# nonlocal a
b = 10
print(b)
a += 1
print(a)
return inner
if __name__ == '__main__':
inner_func = outer()
inner_func()
>> UnboundLocalError: local variable 'a' referenced before assignment
複製代碼
之因此會出現這個錯誤的關鍵在於:當a是數字或任何不可變類型時,a += 1等價於a = a+1,這會把a變爲局部變量。對於不可變類型如數字,字符串,元組來講,只能讀取,不能更新。在a += 1 中,等價于于a = a + 1,這裏隱式建立了一個局部變量a,這樣a就再也不是自由變量了,也不存在在閉包中了。測試
解決方法:加入nonlocal聲明。它的做用是把變量標記爲自由變量,這樣即便在函數中爲變量賦予新值,也會變成自由變量。ui
def outer():
a = 10
def inner():
nonlocal a
b = 10
print(b)
a += 1
print(a)
return inner
if __name__ == '__main__':
inner_func = outer()
inner_func()
>> 10
11
複製代碼
BINGO!
在裝飾器這一部分主要講解如下幾種情形:
函數裝飾器
類裝飾器
裝飾器鏈
帶參數的裝飾器
裝飾器本質上是一個 Python 函數或類,它可讓其餘函數或類在不須要作任何代碼修改的前提下增長額外功能,裝飾器的返回值也是一個函數/類對象。它常常用於有切面需求的場景,好比:插入日誌、性能測試、事務處理、緩存、權限校驗等場景,裝飾器是解決這類問題的絕佳設計。有了裝飾器,咱們就能夠抽離出大量與函數功能自己無關的雷同代碼到裝飾器中並繼續重用。[3]
在網上,各類用的比較多的案例的是,若是咱們有很是多的函數,咱們如今但願統計每個函數的運行時間以及打印其參數應該怎麼作。
比較智障的方法是:修改函數原來的內容,加上統計時間的代碼和打印參數代碼。但結合前面的閉包的知識,咱們應該能夠提出這樣一種方法
def count_time(func):
def wrapper(*args, **kwargs):
tic = time.clock()
func(*args, **kwargs)
toc = time.clock()
print('函數 %s 的運行時間爲 %.4f' %(func.__name__, toc-tic))
print('參數爲:'+str(args)+str(kwargs))
return wrapper
def test_func(*args, **kwargs):
time.sleep(1)
if __name__ == '__main__':
f = count_time(test_func)
f(['hello', 'world'], hello=1, world=2)
複製代碼
在這裏func會綁定給wrapper函數,因此即便count_time函數結束了,其中傳入的func也會綁定給wrapper.下述代碼可驗證之。
f.__code__.co_freevars
>> ('func',)
f.__closure__[0].cell_contents
>> <function test_func at 0x0000014234165AE8>
複製代碼
而上述的代碼就是python裝飾器的原理,只不過在咱們可使用 @
語法糖簡化代碼。
def count_time(func):
def wrapper(*args, **kwargs):
tic = time.clock()
func(*args, **kwargs)
toc = time.clock()
print('函數 %s 的運行時間爲 %.4f' %(func.__name__, toc-tic))
print('參數爲:'+str(args)+str(kwargs))
return wrapper
@count_time
def test_func(*args, **kwargs):
time.sleep(1)
if __name__ == '__main__':
test_func(['hello', 'world'], hello=1, world=2)
>> 函數 test_func 的運行時間爲 1.9999
參數爲:(['hello', 'world'],){'hello': 1, 'world': 2}
複製代碼
一個函數一樣也能夠被多個裝飾器裝飾,其原理於單個裝飾器相同。重要的是理解裝飾器實際上的調用順序。
def count_time(func):
print('count_time_func')
def wrapper_in_count_time(*args, **kwargs):
tic = time.clock()
func(*args, **kwargs)
toc = time.clock()
running_time = toc - tic
print('函數 %s 運行時間 %f'% (func.__name__, running_time))
return wrapper_in_count_time
def show_args(func):
print('show_args func')
def wrapper_in_show_args(*args, **kwargs):
print('函數參數爲'+str(args)+str(kwargs))
return func()
return wrapper_in_show_args
@count_time
@show_args
def test_func(*args, **kwargs):
print('test_func')
if __name__ == '__main__':
f = test_func(['hello', 'world'], hello=1, world=2)
>> show_args func
count_time_func
函數參數爲(['hello', 'world'],){'hello': 1, 'world': 2}
test_func
函數 wrapper_in_show_args 運行時間 0.000025
複製代碼
先忽視@count_time裝飾器,假如只有@show_args裝飾器。 那麼,裝飾器背後實際上是這樣的:
f = show_args(test func)
f(...)
# 加上@count_time後
f = show_args(test func)
g = count_time(f)
g(...)
複製代碼
咱們能夠打印下f,g看下返回的是什麼。
f.__name__
>> wrapper_in_show_args
g.__name__
>> wrapper_in_count_time
複製代碼
因此整個函數的運行流程是: 首先調用了show_args,show_args打印了'show_args_func',以後返回wrapper_in_show_args。接着調用count_time,並把wrapper_in_show_args傳給了count_time,首先打印'count_time_func', 以後返回wrapper_in_count_time.最後用戶調用wrapper_in_count_time函數,並傳入了相關參數。在wrapper_in_count_time函數裏,首先調用了func函數,這裏的func是一個自由變量,即以前傳入的wrapper_in_show_args,因此打印函數參數。在wrapper_in_show_args裏,調用了func(),這裏的func又是以前傳入的test_func,因此打印'test_func'。最後打印函數運行時間,整個調用過程結束。
總而言之,裝飾器的核心就是閉包,只要理解了閉包,就能理解透徹裝飾器。
另外裝飾器不只能夠是函數,還能夠是類,相比函數裝飾器,類裝飾器具備靈活度大、高內聚、封裝性等優勢。使用類裝飾器主要依靠類的__call__方法,當使用 @ 形式將裝飾器附加到函數上時,就會調用此方法。[3]
class deco_class(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('初始化裝飾器')
self.func(*args, **kwargs)
print('停止裝飾器')
@deco_class
def klass(*args, **kwargs):
print(args, kwargs)
if __name__ == '__main__':
klass(['hello', 'world'], hello=1, world=2)
>> 初始化裝飾器
(['hello', 'world'],) {'hello': 1, 'world': 2}
停止裝飾器
複製代碼
[1] www.cnblogs.com/Lin-Yi/p/73…
[2] Fluent Python, Luciano Ramalho