1. 閉包
概念:在函數(shù)嵌套的前提下,內(nèi)層函數(shù)引用了外層函數(shù)的變量(包括參數(shù)),外層函數(shù)又把內(nèi)層函數(shù)當(dāng)做返回值進(jìn)行返回椭员。這個(gè)內(nèi)層函數(shù)+多引用的外層變量,稱為‘閉包’笛园。
實(shí)例1:
def test1(a):
b=10
def test2()
print(a)
print(b)
return test2
應(yīng)用場(chǎng)景:外層函數(shù)隘击,根據(jù)不同的參數(shù)生成不同作用功能的函數(shù)。
注意事項(xiàng):
- 閉包中研铆,如果要修改引用的外層變量埋同,則需要使用nonlocal變量聲明,否則會(huì)當(dāng)做是閉包內(nèi)棵红,新定義的變量凶赁。
- 當(dāng)閉包內(nèi),引用了一個(gè)后期會(huì)發(fā)生變化的變量時(shí)逆甜,一定要注意虱肄。
實(shí)例2:
def test()
funcs = []
for i in range(1, 4):
def test2():
print(i)
funcs.append(test2)
return funcs
newfuncs = test()
newfuncs[0]()
newfuncs[1]()
newfuncs[2]()
>>>3
>>>3
>>>3
以上情況的出現(xiàn)是因?yàn)椋m然在最后newfuncs中調(diào)用不同的test2函數(shù)忆绰,但在此之前funcs函數(shù)內(nèi)的test2沒(méi)有被執(zhí)行浩峡,而test2函數(shù)已經(jīng)被定義了3次了,其中range(1, 4)也已經(jīng)走了3次了错敢,相應(yīng)的i也已經(jīng)從1走到3了翰灾,所以在最后調(diào)用test2的,會(huì)去取i的值稚茅,此時(shí)i=3纸淮,故三次調(diào)用都會(huì)返回3。
案例2改進(jìn):
def test():
funcs = []
for i in range(1, 4):
def test2(num):
def inner():
print(num)
return inner
funcs.append(test2=(i))
return funcs
newfuncs = test()
newfuncs[0]()
newfuncs[1]()
newfuncs[2]()
>>>1
>>>2
>>>3
解釋:在改進(jìn)方案中亚享,其實(shí)就是給之前的test2內(nèi)再嵌套了一個(gè)函數(shù)inner咽块,inner負(fù)責(zé)之前test2的功能,然后每次test2加入funcs列表的時(shí)欺税,直接把的i傳入test2侈沪,同時(shí)又不執(zhí)行inner函數(shù),之后newfuncs調(diào)用test2時(shí)晚凿,其中已經(jīng)包含的每次不同的i的值亭罪,inner函數(shù)執(zhí)行輸出的結(jié)果也就會(huì)不同。
2. 裝飾器
2.1 概念理解
裝飾器本質(zhì)上是一個(gè)函數(shù)歼秽,它可以讓其他函數(shù)在不需要做任何改動(dòng)的前提下增加額外的功能应役,裝飾器的返回值也是一個(gè)函數(shù)對(duì)象。
裝飾器經(jīng)常用于有切面需求的場(chǎng)景:插入日志、性能測(cè)試箩祥、事務(wù)處理院崇、緩存、權(quán)限校驗(yàn)等場(chǎng)景袍祖。
分步理解裝飾器:
(1). 需要給一個(gè)函數(shù)a()添加一個(gè)功能底瓣,功能寫(xiě)在decorator()
內(nèi),當(dāng)我們調(diào)用'decrotor(a)`的時(shí)候就會(huì)在執(zhí)行a()之前執(zhí)行我們要的功能盲泛。
def a():
print('i am a')
def decorator(a):
print('新添加的功能')
a()
(2).我們需要該函數(shù)a()在業(yè)務(wù)邏輯代碼中調(diào)用的時(shí)候名稱還是a()(函數(shù)的單一職責(zé)性)濒持,而不是decrotor(a)
,那么最簡(jiǎn)單的方法就是在把它賦值給a寺滚,即a = decorator(a)
(3). 但是在進(jìn)行a = decorator(a)
時(shí)柑营,右邊的decorator(a)
函數(shù)會(huì)被直接執(zhí)行,而此時(shí)邏輯代碼還沒(méi)有調(diào)用它村视,為避免該情況發(fā)生官套,我們使用閉包的思想。
(4). 這里使用閉包思想:也就是decorator(a)
返回一個(gè)函數(shù)(后面我們會(huì)把這個(gè)函數(shù)寫(xiě)成warpper)蚁孔,將這個(gè)函數(shù)賦值給a奶赔,這樣在進(jìn)行a = decorator(a)
時(shí)右邊就不會(huì)被立即執(zhí)行了。最后我們把需要添加的新功能杠氢,以及a()都寫(xiě)在warpper
函數(shù)里站刑,這樣寫(xiě)就可以使得只有真正在業(yè)務(wù)邏輯代碼中調(diào)用該函數(shù)的時(shí)候,warpper
才會(huì)被執(zhí)行鼻百,同時(shí)我們會(huì)加上一句a = decorator(a)
绞旅,來(lái)確保業(yè)務(wù)邏輯代碼中調(diào)用的時(shí)候名稱還是a()。所以寫(xiě)出的結(jié)果如下:
def a():
print('i am a')
def decorator(a):
def warpper():
print('新添加的功能')
a()
return warpper
a = decorator(a)
a()
# 以上寫(xiě)法中
decorator(a) 等價(jià)于
def warpper():
print('新添加的功能')
a()
最后 Python給我們給定了以個(gè)語(yǔ)法糖@温艇,即可以用@decorator
代替a = decorator(a)
因悲,這也就演變出裝飾器最終的寫(xiě)法:
def decorator(a):
def warpper():
print('新添加的功能')
a()
return warpper
@decorator
def a():
print('i am a')
a()
>>>新添加的功能
>>>i am a
裝飾器采用了閉包的思想,在裝飾函數(shù)的同時(shí)不執(zhí)行函數(shù)勺爱,只有到正正的業(yè)務(wù)邏輯代碼調(diào)用的時(shí)候再執(zhí)行函數(shù)晃琳。
2.2 裝飾器的執(zhí)行時(shí)間
當(dāng)@decorator出現(xiàn)的時(shí)候,decorator裝飾器函數(shù)就立即被執(zhí)行了琐鲁。
2.3 裝飾器的執(zhí)行順序
從上到下去裝飾卫旱,從下到上去執(zhí)行。
@a
@b
@c
def func():
# 等效于
func = a(b(c(func)))
2.4 對(duì)有參數(shù)的函數(shù)進(jìn)行裝飾
如果需要被裝飾的的函數(shù)a()中有參數(shù)呢围段?這時(shí)候就需要有一個(gè)東西來(lái)接收函數(shù)a()中的參數(shù)顾翼,而函數(shù)a()中的參數(shù)也可能是多種多樣的,這時(shí)候我們可以這樣來(lái)寫(xiě)warpper函數(shù):warpper(*args, **kwargs)
蒜撮,但是在warpper函數(shù)中調(diào)用的的func函數(shù)也需要用:func(**args, **kwargs)
的寫(xiě)法來(lái)解包warpper中接收的函數(shù),這樣func才可以正常執(zhí)行。代碼如下:
def decorator(func):
def warpper(*args, **kwargs):
print('新添加的功能')
func(*args, **kwargs)
return warpper
@decorator
def a(i='我', am='是',):
print(i, am, "a")
a()
>>>新添加的功能
>>>我 是 a
2.5 對(duì)有返回值的函數(shù)進(jìn)行裝飾
如果需要被裝飾的的函數(shù)a()中有return返回值呢段磨?這時(shí)候就需要有一個(gè)東西來(lái)返回函數(shù)a()中的返回值取逾,所以我們?cè)趙arpper中也應(yīng)該寫(xiě)入return的部分,代碼如下:
def decorator(func):
def warpper(*args, **kwargs):
print('新添加的功能')
return func(*args, **kwargs)
return warpper
@decorator
def a(i='我', am='是',):
print(i, am, "a")
b = i+am+"b"
return b
b = a()
print(b)
>>>新添加的功能
>>>我 是 a
>>>我是b
原則上要保證苹支,裝飾器中的warpper函數(shù)的格式和被裝飾的函數(shù)格式一致砾隅。
2.6 帶有參數(shù)的裝飾器
如果我們可以給裝飾器本身傳入一個(gè)參數(shù),那么裝飾器就可以隨著傳入?yún)?shù)的變化而做出不同的裝飾功能债蜜,例如:print('新添加的功能')晴埂、print('新添加的技術(shù)')、print('新添加的顏色')寻定。那么儒洛,該如何實(shí)現(xiàn)帶有參數(shù)的裝飾器呢? 思路:我們定義個(gè)一個(gè)外函數(shù)狼速,把裝飾器放入其中琅锻,然后裝飾器中可以使用這個(gè)外函數(shù)傳入的值,同時(shí)在外函數(shù)的最后返回其中的裝飾器向胡,那么這個(gè)整體就可以成為一個(gè)新的帶有參數(shù)的裝飾器恼蓬。代碼如下:
def zhuangshiqi(i):
def decorator(func):
def warpper(*args, **kwargs):
print('新添加的'+i)
return func(*args, **kwargs)
return warpper
return decorator
@zhuangshiqi(i="顏色而不是功能")
def a(i='我', am='是'):
print(i, am, "a")
return a
a()
>>>新添加的顏色而不是功能
>>>我 是 a
實(shí)例1:簡(jiǎn)單的裝飾器
def decorator(func):
def wrapper(*args, *kwargs):
logging.warn("%s is running" % func.__name__)
return func(*args, *kwargs)
return wrapper
@decorator
def a():
print('i am a')
a()
>>> a is running
>>> i am a
實(shí)例2:裝飾器用于驗(yàn)證權(quán)限的例子
userAge = 40
def canYou(func):
def decorator(*args, **kwargs):
if userAge > 1 and userAge < 10:
return func(*args, **kwargs)
print('你的年齡不符合要求,不能看')
return decorator
@canYou
def play():
print('開(kāi)始播放動(dòng)畫(huà)片 《喜洋洋和灰太狼》')
play()
>>> 你的年齡不符合要求僵芹,不能看
內(nèi)置裝飾器
@staticmathod 处硬、@classmethod 、@property