python闭包与装饰器有啥关系_深入理解Python中的闭包与装饰器
函數的裝飾器可以以某種方式增強函數的功能,如在 Flask 中可使用 @app.route('/') 為視圖函數添加路由,是一種十分強大的功能。在表現形式上,函數裝飾器為一種嵌套函數,這其中會涉及到閉包的概念。而在嵌套函數之間,外部函數中的變量相對于內部函數而言為自由變量,使用時可能需要借助于 nonlocal 關鍵字進行聲明。
nonlocal 聲明
按變量的作用域進行分類,Python 中的變量可分為「全局變量」、「局部變量」以及「自由變量」。一般而言,Python 中使用變量前不需要聲明變量,但假定在函數體中賦值的變量為局部變量~除非顯示使用 global 將在函數中賦值的變量聲明為全局變量!
而自由變量則是存在于嵌套函數中的一個概念~定義在其他函數內部的函數被稱之為嵌套函數 nested function ,嵌套函數可以訪問封閉范圍內(外部函數)的變量。嵌套函數不可以在函數外直接訪問。
在 Python 中,非本地變量默認僅可讀取,在修改時必須顯式指出其為非本地變量~自由變量 nonlocal,全局變量 global。>>> ga = 1
>>> def func():
... nb = 2
... def inner():
... ga += 1
... nb += 2
... print('ga is %s, and nb is %s' % (ga, nb))
... return inner
...
>>> test = func()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'ga' referenced before assignment
未加入全局變量和自由變量聲明時且使用賦值操作時,inner 函數的變量 ga, nb 默認為局部變量,會報錯;如注釋掉 ga += 1 后同樣會報錯:Traceback (most recent call last):
...
UnboundLocalError: local variable 'nb' referenced before assignment
可行改寫如下:>>> ga = 1
>>> def func():
... nb = 2
... def inner():
... global ga
... nonlocal nb
... ga += 1
... nb += 2
... print('ga is %s, and nb is %s' % (ga, nb))
... return inner
...
>>> test = func()
>>> test()
ga is 2, and nb is 4
>>> test()
ga is 3, and nb is 6
通過顯示聲明 ga, nb 分別為「全局變量」和「自由變量」,此時如預期運行!
閉包
函數內的函數以及其自由變量形成閉包。也即閉包是一種保留定義函數時存在的自由變量的綁定的函數~這樣在調用函數時,綁定的自由變量依舊可用。
閉包可以避免全局變量的使用以及提供某種形式的數據隱藏。當函數中的變量和函數較少且其中某個功能常用時,使用閉包來進行封裝。當變量和函數更加復雜時,則使用類來實現。# 計算移動平均值的函數
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
那么此時,make_averager() 函數的第二行 series = [] 到第七行 return total/len(series) 為閉包,變量 series 為 averager() 函數中的自由變量!# avg 為一個 averager 函數對象 ~ 含自由變量的綁定
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11
# 創建另一個 averager 函數對象
>>> avg2 = make_averager()
>>> avg2(1)
1.0
>>> avg2(18)
9.5
# 查看 avg, avg2 自由變量中保存的值
>>> avg.__closure__[0].cell_contents
[10, 11, 12]
>>> avg2.__closure__[0].cell_contents
[1, 18]
函數對象通過 __closure__ 屬性 —— 返回 cell 對象元祖(函數中有多少嵌套函數則該元祖的長度有多長),生成該對象的函數被稱之為閉包函數。
func.__closure__[0].cell_contents: 訪問存儲在 cell 對象中值。
裝飾器
裝飾器本身是一個可調用的對象~函數或類,其參數為另一個函數(被裝飾的函數)。裝飾器可能會處理被裝飾的函數(如添加一些功能)然后將之返回,或者將之替換為另一個函數或可調用對象。這也被稱之為元編程 metaprogramming —— 在編譯時改變函數功能。>>> def make_pretty(func):
... def inner():
... print("I got decorated!", end='')
... func()
... return inner
...
>>> def ordinary():
... print("I am ordinary!")
# 用 make_pretty 函數裝飾 ordinary 函數
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated! I am ordinary!
可以作為裝飾器的函數內部都有嵌套的功能函數(用以實現主要功能),并返回內部的嵌套函數。@make_pretty
def ordinary():
print("I am ordinary!")
# 等價于
def ordinary():
print("I am ordinary!")
ordianry = make_pretty(ordinary)
make_pretty(func) 是一個最簡單的裝飾器,它接受一個函數為其參數;內部定義了一個 inner() 函數~輸出 "I got decorated!" 后執行被裝飾函數(此時 func 為 inner 閉包中的自由變量);然后返回內部函數 inner。
此時,對于被裝飾的函數 ordinary 而言,此時是 inner 的引用:>>> ordinary()
I got decorated! I am ordinary!
>>> ordinary
.inner at 0x10aeaa1e0>
除了最簡單的裝飾器之外,還可以將多個裝飾器疊放使用
疊放裝飾器def star(func):
def inner(*args, **kwargs):
print('*' * 30)
func(*args, **kwargs)
print('*' * 30)
return inner
def dollar(func):
def inner(*args, **kwargs):
print('$' * 30)
func(*args, **kwargs)
print('$' * 30)
return inner
@star
@doller
def printer(msg):
print(msg)
printer("Hello world!")
# 結果如下
'''
******************************
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
Hello world!
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
******************************
'''
# 等價于
def printer(msg):
print(msg)
printer = star(dollar(printer))
總結
以上是生活随笔為你收集整理的python闭包与装饰器有啥关系_深入理解Python中的闭包与装饰器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Monster Audio 使用教程(三
- 下一篇: websocket python爬虫_p