python之理解閉包和裝飾器
1、閉包函數(shù)
1.1 python中函數(shù)都是對象
def shut(word='yes'):
print(word.capitalize())
shut()
scream = shut
scream()
print(type(shut))
print(id(shut))
print(id(scream))
del shut
scream()
print(id(scream))
結(jié)果:
Yes
Yes
<class 'function'>
33454008
33454008
Yes
33454008
上面定義一個shut函數(shù)雌团,由type獲取其類型可知是class燃领,那它當(dāng)然是一個對象了(具體可參考理解元類)。
對象的話锦援,我們就知道可以賦值給另外一個變量猛蔽,可以在其它函數(shù)中定義,且函數(shù)可以返回另一個函數(shù)灵寺。
shut()
這里是函數(shù)調(diào)用曼库,scream = shut
函數(shù)引用,scream()
調(diào)用略板,然后下面打印它們的內(nèi)存地址是一樣的凉泄,再下面刪除del shut
變量,調(diào)用scream()
,內(nèi)存地址不變蚯根,這里可以想到另外一個東西:python的垃圾回收機制后众,其它理解系列會學(xué)習(xí)下。
1.2 函數(shù)引用與閉包
這個嘮叨要一段時間颅拦,詳細了解引用js的介紹javascript之理解閉包蒂誉。
簡單來說,閉包是滿足下面幾個東西的函數(shù):
- 嵌套函數(shù)距帅,即一個函數(shù)內(nèi)部定義另外一個函數(shù)右锨,外層函數(shù)稱為A,內(nèi)層函數(shù)稱為B碌秸。
- 在嵌套函數(shù)中的內(nèi)層函數(shù)B在外層函數(shù)A中所在的全局作用域范圍被調(diào)用绍移。
- 這時調(diào)用B函數(shù)的作用域有兩個:A所在的全局作用域和B所在A中的局部作用域。
再簡單來說就是:閉包就是能夠讀取其它函數(shù)內(nèi)部變量的函數(shù)讥电。這里可以理解B是閉包函數(shù)蹂窖,然后A是其它函數(shù)。
一個簡單例子:
def talk(name='shut'):
who = 'daocoder'
def shut(word='yes'):
print('shut %s to %s' % (word.upper(), who))
def whisper(word='yes'):
print('whisper %s to %s' % (word.lower(), who))
if name == 'shut':
return shut
elif name == 'whisper':
return whisper
else:
return shut
print(talk())
talk()(word='mudai')
talk('whisper')(word='mudai')
結(jié)果:
<function talk.<locals>.shut at 0x0000000001F27840>
shut MUDAI to daocoder
whisper mudai to daocoder
在talk
函數(shù)內(nèi)部再定義shut
和whisper
函數(shù)恩敌,并且兩個函數(shù)用到了外邊函數(shù)的變量who
和name
瞬测,那么這兩個函數(shù)以及用到的一些變量稱之為閉包。
1.3 閉包啥作用呢
def line_conf(a, b):
def line(x):
return a * x + b
return line
line1 = line_conf(2, 5)
line2 = line_conf(1, 3)
print(line1(2))
print(line2(2))
結(jié)果:
9
5
上面的例子中纠炮,函數(shù)line
和變量a
月趟、b
就構(gòu)成了閉包。在創(chuàng)建閉包的時候恢口,我們通過line_conf(a, b)說明了變量的取值孝宗,那么這樣就確定了最終閉包函數(shù)(y=2x+5和y=x+3)。且我們只需要變換不同的參數(shù)就可以獲取不同的直線函數(shù)表達式耕肩。這里體現(xiàn)代碼的復(fù)用性因妇。另外可以因此隱藏內(nèi)部實現(xiàn)細節(jié)问潭。
注意:由于閉包引用了外部函數(shù)的局部變量,那么外部函數(shù)的局部變量就一直會存在于內(nèi)存中沙峻,內(nèi)存泄漏是一個隱患。
2两芳、裝飾器
提到裝飾器摔寨,應(yīng)該提及另一個東西AOP(Aspect Origented Programming)面向切換編程。它的作用很強大怖辆,聯(lián)想實際業(yè)務(wù)的場景是复,我們想用戶在開始訪問時記錄一個日志,即整個框架跑起來初始化的時候竖螃,解析請求路由之前做這些事情淑廊。另外可用的場景有插入日志、性能測試特咆、事務(wù)處理(登錄)等季惩。
裝飾器即基于AOP思想的一個實現(xiàn),也利用到了上節(jié)說的閉包腻格。有了裝飾器画拾,我們就可以抽離出大量與函數(shù)本身無關(guān)的代碼并重復(fù)加以利用,概況的說:裝飾器就是為已存在的對象添加額外的功能菜职。
2.1 python里的裝飾器
def makebold(fn):
def wrapped():
return '<b>' + fn() + '</b>'
return wrapped
def makeitalic(fn):
def wrapped():
return '<i>' + fn() + '</i>'
return wrapped
@makebold
@makeitalic
def say():
return "Hello"
print(say())
結(jié)果:
<b><i>Hello</i></b>
效果就如上青抛,那繼續(xù)討論輪子是怎么造的。
2.2 手動實現(xiàn)裝飾器
def my_decorator(fn):
def wrapped(*args, **kwargs):
new_name = kwargs['name'].replace('daocoder', 'mudai')
new_age = args[0] + 1
return fn(new_age, name=new_name)
return wrapped
def say_name(*args, **kwargs):
return "my name is %s and %d years old" % (kwargs['name'], args[0])
print(say_name(26, name='daocoder'))
print(my_decorator(say_name)(26, name='daocoder'))
結(jié)果:
print(say_name(26, name='daocoder'))
print(my_decorator(say_name)(26, name='daocoder'))
看著有些別扭酬核、大體想法希望能表達出來蜜另。以上的調(diào)用過程如下:
1、say_name作為參數(shù)傳遞給my_decorator后嫡意,say_name指向my_decorator返回的wrapped举瑰;
2、然后wrapped作為一個新的函數(shù)去調(diào)用蔬螟。
3嘶居、內(nèi)部函數(shù)wrapped被引用,外部函數(shù)的變量fn(即say_name)并沒有被釋放促煮,它保存的還是原來最初的定義邮屁。
2.3 python語法糖@的用法
稍微修改上面的輪子:
def my_decorator(fn):
def wrapped(*args, **kwargs):
new_name = kwargs['name'].replace('daocoder', 'mudai')
new_age = args[0] + 1
return fn(new_age, name=new_name)
return wrapped
@my_decorator
def say_name(*args, **kwargs):
return "my name is %s and %d years old" % (kwargs['name'], args[0])
print(say_name(26, name='daocoder'))
結(jié)果:
my name is mudai and 27 years old
那說明這個my_decorator(say_name)
和@my_decorator
這兩個的作用就是一樣的。一個語法糖包裝吧菠齿。
@my_decorator == my_decorator(say_name)
2.4 類裝飾器
上面的介紹可以看出佑吝,裝飾器需要callable對象作為參數(shù),然后返回一個callback對象绳匀。一般在python中callable對象都是函數(shù)芋忿,但也有例外炸客,只有某個類中寫了__call__
方法,那么對象就是callable的戈钢。
class MyDecorator(object):
def __init__(self, func):
self.func = func
pass
def __call__(self, *args, **kwargs):
# print(args)
# print(kwargs)
# print(self.func)
new_name = kwargs['name'].replace('daocoder', 'mudai')
new_age = args[0] + 1
return self.func(new_age, name=new_name)
@MyDecorator
def say_name(*args, **kwargs):
return "my name is %s and %d years old" % (kwargs['name'], args[0])
print(say_name(26, name='daocoder'))
結(jié)果:
my name is mudai and 27 years old
以上大體調(diào)用過程如下:
1痹仙、MyDecorator
作為裝飾器對say_name
進行裝飾的時候,這時先去創(chuàng)建MyDecorator
這個實例對象殉了。
2开仰、創(chuàng)建對象初始化時,會把say_name
這個函數(shù)名當(dāng)作參數(shù)傳遞到__init__
方法中薪铜,這里保存say_name
作為對象的func
屬性众弓。
3、這時say_name
指向的是MyDecorator
的實例對象隔箍,然后調(diào)用say_name()
谓娃,即調(diào)用MyDecorator的實例對象的
call`方法。
這里和之前函數(shù)作為裝飾器的區(qū)別就是第3步:函數(shù)作為裝飾器時蜒滩,say_name的指向應(yīng)該是返回的wrapped方法滨达,而這里say_name的指向是MyDecorator的實例對象。
3俯艰、感謝
某平臺培訓(xùn)資料弦悉。