python總結(jié)之閉包和裝飾器
一、裝飾器
1. 裝飾器的簡單介紹
“裝飾器的功能是將被裝飾的函數(shù)當(dāng)作參數(shù)傳遞給與裝飾器對應(yīng)的函數(shù)(名稱相同的函數(shù)),并返回包裝后的被裝飾的函數(shù)”,聽起來有點(diǎn)繞,沒關(guān)系,直接看示意圖,其中 a 為與裝飾器 @a 對應(yīng)的函數(shù), b 為裝飾器修飾的函數(shù),裝飾器@a的作用是:
舉個栗子:
def test(func): return func @test def afunc(): print("hello") afunc() # hello
上面使用@test來表示裝飾器,其等同于:afunc = test(afunc),因此裝飾器本質(zhì)上就是個語法糖,其作用為簡化代碼,以提高代碼可讀性。
2. 裝飾器的解析過程
step1. python
解釋器發(fā)現(xiàn)@test,就去調(diào)用與其對應(yīng)的test函數(shù)
step2. test
函數(shù)調(diào)用前要指定一個參數(shù),傳入的就是@test下面修飾的函數(shù),也就是afunc()
step3. test()
函數(shù)執(zhí)行,調(diào)用 afunc(),afunc()
打印“hello”
二、閉包
在計(jì)算機(jī)科學(xué)中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數(shù)。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。閉包在運(yùn)行時可以有多個實(shí)例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實(shí)例。
閉包并不是Python中特有的概念,所有把函數(shù)做為一等公民的語言均有閉包的概念。不過像Java這樣以class為一等公民的語言中也可以使用閉包,只是它得用類或接口來實(shí)現(xiàn)。
通過Python的語言介紹,一個閉包就是你調(diào)用了一個函數(shù)A,這個函數(shù)A返回了一個函數(shù)B給你。這個返回的函數(shù)B就叫做閉包。你在調(diào)用函數(shù)A的時候傳遞的參數(shù)就是自由變量。
舉個栗子:
def func(name): def inner_func(age): print 'name:', name, 'age:', age return inner_func bb = func('the5fire') bb(26) # >>> name: the5fire age: 26
這里面調(diào)用func的時候就產(chǎn)生了一個閉包——inner_func,并且該閉包持有自由變量——name,因此這也意味著,當(dāng)函數(shù)func的生命周期結(jié)束之后,name這個變量依然存在,因?yàn)樗婚]包引用了,所以不會被回收。
三、閉包中nonlocal語句的使用
1. 外部變量的引用和改寫
在 python 的函數(shù)內(nèi),可以直接引用外部變量,但不能改寫外部變量。
例如在下面的栗子,
counter中可以正常打印常量count,但無法改變count;對list可以執(zhí)行append操作,正常修改。
def cnt(param): count = 0 alist = [] def counter(): alist.append(1) # count += 1 # UnboundLocalError: local variable 'count' referenced before assignment print(param, str(count), alist) return counter test = cnt("test") test() # test 0 [1]
2. nolocal的使用及特點(diǎn)
為了解決上述不可變變量的修改問題:
python 2 中可以在函數(shù)內(nèi)使用 global 語句,但全局變量在任何語言中都不被提倡,因?yàn)樗茈y控制。python 3 中引入了 nonlocal 語句解決了這個問題。
Nonlocal 與 global 的區(qū)別在于:nonlocal 語句會去搜尋本地變量與全局變量之間的變量,其會優(yōu)先尋找層級關(guān)系與閉包作用域最近的外部變量。
def cnt(param): count = 0 def counter(): nonlocal count count += 1 print(param, str(count)) return counter test = cnt("test") test() # test 1
四、閉包與裝飾器
上面已經(jīng)簡單演示了裝飾器的功能,事實(shí)上,裝飾器就是一種的閉包的應(yīng)用,只不過其傳遞的(自由變量)是函數(shù):
使用裝飾器的寫法:
def make1(fn): def wrapped(): return "<a>" + fn() + "</a>" return wrapped def make2(fn): def wrapped(): return "<b>" + fn() + "</b>" return wrapped @make1 @make2 def hello(): return "hello" print(hello()) # <a><b>hello</b></a>
顯式使用閉包的寫法:
def make1(fn): def wrapped(): return "<a>" + fn() + "</a>" return wrapped def make2(fn): def wrapped(): return "<b>" + fn() + "</b>" return wrapped def hello(): return "hello" hello = make2(hello) hello = make1(hello) print(hello()) # <a><b>hello</b></a>
多個裝飾器裝飾一個函數(shù)時,執(zhí)行時的順序是:最先裝飾的裝飾器,最后一個執(zhí)行。它遵循了先進(jìn)后出規(guī)則 類似于stack。
五、閉包的作用
閉包的最大特點(diǎn)是可以將父函數(shù)的變量與內(nèi)部函數(shù)綁定,并返回綁定變量后的函數(shù)(也即閉包),此時即便生成閉包的環(huán)境(父函數(shù))已經(jīng)釋放,閉包仍然存在。
這個過程很像類(父函數(shù))生成實(shí)例(閉包),不同的是父函數(shù)只在調(diào)用時執(zhí)行,執(zhí)行完畢后其環(huán)境就會釋放,而類則在文件執(zhí)行時創(chuàng)建,一般程序執(zhí)行完畢后作用域才釋放,因此對一些需要重用的功能且不足以定義為類的行為,使用閉包會比使用類占用更少的資源,且更輕巧靈活。
假設(shè)我們僅僅想打印出各類動物的叫聲,分別以類和閉包來實(shí)現(xiàn):
樣的,但顯然類的實(shí)現(xiàn)相對繁瑣,且這里只是想輸出一下動物的叫聲,定義一個 Animal 類未免小題大做,而且 voice 函數(shù)在執(zhí)行完畢后,其作用域就已經(jīng)釋放,但 Animal 類及其實(shí)例 dog 的相應(yīng)屬性卻一直貯存在內(nèi)存中。
除此之外,閉包還有很多其他功能,比如用于封裝等,另外,閉包有效的減少了函數(shù)參數(shù)的數(shù)目,這對并行計(jì)算非常有價(jià)值,比如可以讓每臺電腦負(fù)責(zé)一個函數(shù),然后串起來,實(shí)現(xiàn)流水化的作業(yè)等。
六、幾個小栗子
栗子1:
def outer(f): def inner(*arg, **kargs): inner.co += 1 return f(*arg, **kargs) inner.co = 0 return inner @outer def cu(): pass if __name__ == '__main__': cu() cu() cu() print(cu.co) # 3
栗子2:
下述樣例中,注意點(diǎn):
- 首先解析裝飾器A,裝飾器裝飾了幾個類就執(zhí)行幾次,輸出兩次“i賦值”
- B和C為兩個對象,屬性值單獨(dú)累加。
def A(func): def inner(): inner.i += 1 print("i加1,i={0}".format(inner.i)) inner.i = 0 print("i賦值") return inner @A def B(): pass @A def C(): pass B() B() B() C() C() print(id(B), id(B.i)) print(id(C), id(C.i))
i賦值
i賦值
i加1,i=1
i加1,i=2
i加1,i=3
i加1,i=1
i加1,i=2
281473235252496 187650677653032
281473235252768 187650677653000
栗子3
- 裝飾器是在python解釋器加載test函數(shù)的時候就完成的,即使不調(diào)用test函數(shù),也會輸出"bbb"和"aaa",輸出順序?yàn)?,dec_b裝飾了test,執(zhí)行輸出bbb,dec_a裝飾了dec_b,執(zhí)行輸出aaa;
- 執(zhí)行test等同于執(zhí)行dec_a(dec_b(test))
def dec_a(function): print("aaa") def inner_func(): print("before function") function() return inner_funcdef dec_b(function): print("bbb") def inner_func(): function() print("after function") return inner_func@dec_a@dec_bdef test(): print("test")test()
bbbaaabefore functiontestafter function
七、特殊的裝飾器
property 裝飾器
參考這篇文章:https://www.tianqiweiqi.com/python-property.html
property 是Python中很贊的概念,它使得面向?qū)ο蟮木幊谈雍唵巍?/p>
在Python中,property()是一個內(nèi)置函數(shù),用于創(chuàng)建和返回一個property對象。Property對象有三個方法,getter(), setter()和delete(),用來在對象創(chuàng)建后設(shè)置fget,fset和fdel。
裝飾器(decorator)可以給函數(shù)動態(tài)加上功能,對于類的方法,裝飾器一樣起作用。Python內(nèi)置的@property裝飾器就是負(fù)責(zé)把一個方法變成屬性調(diào)用的。屬性是對事物某種特性的抽象,面向?qū)ο缶幊讨幸粋€重要概念;區(qū)別于字段,它通常表示為字段的擴(kuò)展,加以訪問與設(shè)置保護(hù)機(jī)制。
1. 我們?yōu)槭裁葱枰玫絧roperty
博文中假設(shè)了一種場景,假設(shè)我們有一個存儲并轉(zhuǎn)化溫度的需求,可以通過類實(shí)現(xiàn):
class Celsius: def __init__(self, temperature = 0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32
然后通過類實(shí)例進(jìn)行溫度的設(shè)定和獲取,且可以看到這個屬性已經(jīng)被添加man.__dict__中了。
>>> man = Celsius() >>> man.temperature = 37 >>> man.temperature 37 >>> man.to_fahrenheit() 98.60000000000001 >>> man.__dict__ {'temperature': 37}
但是此時如果我們需要對溫度的設(shè)定進(jìn)行一定的約束,此前的方案是沒辦法做到的。
2. 使用Getters和Setters
對于上邊的約束,一個很容易想到的解決方案是隱藏其溫度屬性(使其私有化),并且定義新的用于操作溫度屬性的getter和setter接口??梢赃@么實(shí)現(xiàn):
class Celsius: def __init__(self, temperature = 0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 def get_temperature(self): return self._temperature def set_temperature(self, value): if value < -273: raise ValueError("Temperature below -273 is not possible") self._temperature = value
上述方案雖然滿足了基本需求,但是有個問題是,在賦值和調(diào)用時,需要修改調(diào)用方式,例如obj.temperature需改為obj.get_temperature()
,obj.temperature = val
改為obj.set_temperature(val)。
我們希望我們的更新是不向后兼容地。這就是需要property閃亮登場的地方。
3. property的作用
對于上邊的問題,Python式的解決方式是使用property,在setter中進(jìn)行參數(shù)校驗(yàn):
class Celsius: def __init__(self, temperature = 0): self._temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 @property def temperature(self): print("Getting value") return self._temperature @temperature.setter def temperature(self, value): if value < -273: raise ValueError("Temperature below -273 is not possible") print("Setting value") self._temperature = value
在Python中,property()是一個內(nèi)置函數(shù),用于創(chuàng)建和返回一個property對象。該函數(shù)的簽名為:
property(fget=None, fset=None, fdel=None, doc=None)
只定義getter方法,不定義setter方法就是一個只讀屬性;
否則為可讀可寫屬性,且在setter中進(jìn)行參數(shù)校驗(yàn)。
4. 小栗子
class Student(): def __init__(self): self._score = 10000 @property def score(self): return self._score @score.setter def score(self, value): if value < 0: print("wrong value") return self._score = value @score.deleter def score(self): del self._score a = Student() a.score = 99 print(a.score) del a.score a.score = -1 print(a.score) # 99 # wrong value # AttributeError: 'Student' object has no attribute '_score'
staticmethod裝飾器和classmethod裝飾器
python面向?qū)ο缶幊讨?,類中定義的方法:
- @classmethod 裝飾的類方法:第一個參數(shù)必須是cls
- @staticmethod 裝飾的靜態(tài)方法:和普通的函數(shù)沒有區(qū)別
- 不帶裝飾器的實(shí)例方法:第一個參數(shù)必須是 self
以一個簡單的代碼為例,執(zhí)行方式如下:
class A(object): # 創(chuàng)建一個類對象,初始化類屬性和方法 def m1(self, n): print("self:", self) @classmethod def m2(cls, n): print("cls:", cls) @staticmethod def m3(n): pass a = A() # 調(diào)用類構(gòu)造器,構(gòu)造實(shí)例對象a a.m1(1) # 內(nèi)部把[實(shí)例對象a]傳遞給[self]進(jìn)行綁定,self和a指向同一個實(shí)例對象。 A.m2(1) # 內(nèi)部把[類對象A]傳遞給[cls],cls和A都指向類對象。 A.m3(1)
下面分別使用不同的類方法進(jìn)行代碼的測試:
step1:定義實(shí)例方法count()。
Spam.numInstances
為類調(diào)用,直接返回初始化的99;x.numInstances
為實(shí)例化調(diào)用,在實(shí)例化時調(diào)用了init構(gòu)造方法,調(diào)用了實(shí)例方法count,在99的基礎(chǔ)上加1。
Sub.numInstances
, Other.numInstances
為類調(diào)用,直接返回初始化的1;y1.numInstances
, z1.numInstances
為實(shí)例化調(diào)用,由于sub和other子類繼承了父類spam,且在內(nèi)部沒有定義init方法,因此返回父類的init,調(diào)用count,在初始化的基礎(chǔ)上加1。
class Spam: numInstances = 99 def count(self): self.numInstances += 1 def __init__(self): self.count() class Sub(Spam): numInstances = 0 class Other(Spam): numInstances = 0 x = Spam() y1, y2 = Sub(), Sub() z1, z2, z3 = Other(), Other(), Other() print(x.numInstances, y1.numInstances, z1.numInstances) print(Spam.numInstances, Sub.numInstances, Other.numInstances)
100 1 1
99 0 0
step2:定義靜態(tài)方法count()。
每次實(shí)例化都會調(diào)用init方法,調(diào)用count對類屬性Spam.numInstances
的值進(jìn)行累加,因此實(shí)例化幾次,就會累加多少次。
class Spam: numInstances = 99 @staticmethod def count(): Spam.numInstances += 1 def __init__(self): self.count() class Sub(Spam): numInstances = 0 class Other(Spam): numInstances = 0 x = Spam() y1, y2 = Sub(), Sub() z1, z2, z3 = Other(), Other(), Other() print(x.numInstances, y1.numInstances, z1.numInstances) print(Spam.numInstances, Sub.numInstances, Other.numInstances)
105 0 0
105 0 0
step3:定義類方法count()。
在實(shí)例化Sub和Other子類時,子類內(nèi)部定義了numInstances,因此會在cls.numInstances += 1時,分別在Sub和Other各自的numInstances 分別進(jìn)行累加,實(shí)例化多少次,進(jìn)行多少次累加。
class Spam: numInstances = 99 @classmethod def count(cls): cls.numInstances += 1 def __init__(self): self.count() class Sub(Spam): numInstances = 0 class Other(Spam): numInstances = 0 x = Spam() y1, y2 = Sub(), Sub() z1, z2, z3 = Other(), Other(), Other() print(x.numInstances, y1.numInstances, z1.numInstances) print(Spam.numInstances, Sub.numInstances, Other.numInstances)
100 2 3
100 2 3
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Python編程中內(nèi)置的NotImplemented類型的用法
這篇文章主要介紹了Python編程中內(nèi)置的NotImplemented類型的用法,NotImplemented 是Python在內(nèi)置命名空間中的六個常數(shù)之一,下文更多詳細(xì)內(nèi)容需要的小伙伴可以參考一下2022-03-03NVIDIA安裝CUDA的實(shí)現(xiàn)(圖文教程)
本文主要介紹了NVIDIA安裝CUDA的實(shí)現(xiàn),包括系統(tǒng)要求、軟件下載、安裝步驟以及常見問題解決,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Flask框架debug與配置項(xiàng)的開啟與設(shè)置詳解
這篇文章主要介紹了Flask框架debug與配置項(xiàng)的開啟與設(shè)置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09