python總結(jié)之閉包和裝飾器
一、裝飾器
1. 裝飾器的簡單介紹
“裝飾器的功能是將被裝飾的函數(shù)當作參數(shù)傳遞給與裝飾器對應的函數(shù)(名稱相同的函數(shù)),并返回包裝后的被裝飾的函數(shù)”,聽起來有點繞,沒關(guān)系,直接看示意圖,其中 a 為與裝飾器 @a 對應的函數(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)用與其對應的test函數(shù)
step2. test函數(shù)調(diào)用前要指定一個參數(shù),傳入的就是@test下面修飾的函數(shù),也就是afunc()
step3. test() 函數(shù)執(zhí)行,調(diào)用 afunc(),afunc() 打印“hello”
二、閉包
在計算機科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變量的函數(shù)。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外。所以,有另一種說法認為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環(huán)境和相同的函數(shù)組合可以產(chǎn)生不同的實例。
閉包并不是Python中特有的概念,所有把函數(shù)做為一等公民的語言均有閉包的概念。不過像Java這樣以class為一等公民的語言中也可以使用閉包,只是它得用類或接口來實現(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,因此這也意味著,當函數(shù)func的生命周期結(jié)束之后,name這個變量依然存在,因為它被閉包引用了,所以不會被回收。
三、閉包中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的使用及特點
為了解決上述不可變變量的修改問題:
python 2 中可以在函數(shù)內(nèi)使用 global 語句,但全局變量在任何語言中都不被提倡,因為它很難控制。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ù):
使用裝飾器的寫法:
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í)行。它遵循了先進后出規(guī)則 類似于stack。
五、閉包的作用
閉包的最大特點是可以將父函數(shù)的變量與內(nèi)部函數(shù)綁定,并返回綁定變量后的函數(shù)(也即閉包),此時即便生成閉包的環(huán)境(父函數(shù))已經(jīng)釋放,閉包仍然存在。
這個過程很像類(父函數(shù))生成實例(閉包),不同的是父函數(shù)只在調(diào)用時執(zhí)行,執(zhí)行完畢后其環(huán)境就會釋放,而類則在文件執(zhí)行時創(chuàng)建,一般程序執(zhí)行完畢后作用域才釋放,因此對一些需要重用的功能且不足以定義為類的行為,使用閉包會比使用類占用更少的資源,且更輕巧靈活。
假設(shè)我們僅僅想打印出各類動物的叫聲,分別以類和閉包來實現(xiàn):

樣的,但顯然類的實現(xiàn)相對繁瑣,且這里只是想輸出一下動物的叫聲,定義一個 Animal 類未免小題大做,而且 voice 函數(shù)在執(zhí)行完畢后,其作用域就已經(jīng)釋放,但 Animal 類及其實例 dog 的相應屬性卻一直貯存在內(nèi)存中。
除此之外,閉包還有很多其他功能,比如用于封裝等,另外,閉包有效的減少了函數(shù)參數(shù)的數(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:
下述樣例中,注意點:
- 首先解析裝飾器A,裝飾器裝飾了幾個類就執(zhí)行幾次,輸出兩次“i賦值”
- B和C為兩個對象,屬性值單獨累加。
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",輸出順序為,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裝飾器就是負責把一個方法變成屬性調(diào)用的。屬性是對事物某種特性的抽象,面向?qū)ο缶幊讨幸粋€重要概念;區(qū)別于字段,它通常表示為字段的擴展,加以訪問與設(shè)置保護機制。
1. 我們?yōu)槭裁葱枰玫絧roperty
博文中假設(shè)了一種場景,假設(shè)我們有一個存儲并轉(zhuǎn)化溫度的需求,可以通過類實現(xiàn):
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
然后通過類實例進行溫度的設(shè)定和獲取,且可以看到這個屬性已經(jīng)被添加man.__dict__中了。
>>> man = Celsius()
>>> man.temperature = 37
>>> man.temperature
37
>>> man.to_fahrenheit()
98.60000000000001
>>> man.__dict__
{'temperature': 37}
但是此時如果我們需要對溫度的設(shè)定進行一定的約束,此前的方案是沒辦法做到的。
2. 使用Getters和Setters
對于上邊的約束,一個很容易想到的解決方案是隱藏其溫度屬性(使其私有化),并且定義新的用于操作溫度屬性的getter和setter接口??梢赃@么實現(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中進行參數(shù)校驗:
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中進行參數(shù)校驗。
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ù)必須是 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)造實例對象a
a.m1(1) # 內(nèi)部把[實例對象a]傳遞給[self]進行綁定,self和a指向同一個實例對象。
A.m2(1) # 內(nèi)部把[類對象A]傳遞給[cls],cls和A都指向類對象。
A.m3(1)
下面分別使用不同的類方法進行代碼的測試:
step1:定義實例方法count()。
Spam.numInstances為類調(diào)用,直接返回初始化的99;x.numInstances為實例化調(diào)用,在實例化時調(diào)用了init構(gòu)造方法,調(diào)用了實例方法count,在99的基礎(chǔ)上加1。
Sub.numInstances, Other.numInstances為類調(diào)用,直接返回初始化的1;y1.numInstances, z1.numInstances為實例化調(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()。
每次實例化都會調(diào)用init方法,調(diào)用count對類屬性Spam.numInstances的值進行累加,因此實例化幾次,就會累加多少次。
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()。
在實例化Sub和Other子類時,子類內(nèi)部定義了numInstances,因此會在cls.numInstances += 1時,分別在Sub和Other各自的numInstances 分別進行累加,實例化多少次,進行多少次累加。
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保存網(wǎng)頁上的圖片或者保存頁面為截圖
這篇文章主要介紹了使用Python保存網(wǎng)頁上的圖片或者保存頁面為截圖的方法,保存網(wǎng)頁圖片主要用到urllib模塊,即簡單的爬蟲原理,需要的朋友可以參考下2016-03-03
在Python中使用CasperJS獲取JS渲染生成的HTML內(nèi)容的教程
這篇文章主要介紹了在Python中使用CasperJS獲取JS渲染生成的HTML內(nèi)容的教程,需要先用JavaScript創(chuàng)建一個接口文件,需要的朋友可以參考下2015-04-04
Visual Studio code 配置Python開發(fā)環(huán)境
這篇文章主要介紹了Visual Studio code 配置Python開發(fā)環(huán)境,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09
解決Django 在ForeignKey中出現(xiàn) non-nullable field錯誤的問題
今天小編就為大家分享一篇解決Django 在ForeignKey中出現(xiàn) non-nullable field錯誤的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08
Python實現(xiàn)自動發(fā)消息自定義內(nèi)容的操作代碼
這篇文章主要介紹了Python實現(xiàn)自動發(fā)消息自定義內(nèi)容的操作代碼,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
python利用json和pyecharts畫折線圖實例代碼
這篇文章主要介紹了python利用json和pyecharts畫折線圖實例,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12

