小白在學(xué)習(xí)裝飾器時(shí),會(huì)遇到一些地方不太理解或者不太清楚,這是因?yàn)橐婚_(kāi)始你就直接擼裝飾器的緣故,那么怎樣才能將裝飾器理解并且弄懂呢?
所以在學(xué)裝飾器之前必須要弄懂函數(shù)的嵌套以及閉包,接下來(lái)我用嵌套--->閉包--->裝飾器這個(gè)順序來(lái)講解,希望對(duì)各位有用。
創(chuàng)新互聯(lián)2013年至今,是專(zhuān)業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都做網(wǎng)站、成都網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元梁山做網(wǎng)站,已為上家服務(wù),為梁山各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:028-86922220
1、首先要弄懂,函數(shù)的作用域的概念:Python中以函數(shù)為作用域,在作用域中定義的相關(guān)數(shù)據(jù)只能被當(dāng)前作用域或子作用域使用。
同時(shí)函數(shù)也是定義在作用域中的數(shù)據(jù),在執(zhí)行函數(shù)時(shí)候,也同樣遵循:優(yōu)先在自己作用域中尋找,沒(méi)有則向上一作用域?qū)ふ?/p>
def func():
print("你好")
func()
def execute():
print("開(kāi)始")
func()
print("結(jié)束")
def func():
print(666)
func()
execute()
認(rèn)真執(zhí)行上面的代碼,你就能發(fā)現(xiàn)函數(shù)在全局作用域中的一些規(guī)則。也就是函數(shù)名相同的函數(shù),新的函數(shù)會(huì)覆蓋就的函數(shù)。
2、上述示例中的函數(shù)均定義在全局作用域,其實(shí)函數(shù)也可以定義在局部作用域,這樣函數(shù)被局部作用域和其子作用于中調(diào)用(函數(shù)的嵌套)。
def func():
print("")
def inner():
print("-------------")
def handler():
print("")
def inner():
print("")
inner()
func()
print("")
handler()
分析上面可知:函數(shù)先調(diào)用自己作用域內(nèi)部的函數(shù),若作用域內(nèi)部沒(méi)有,則從外部去找。
現(xiàn)在的你可能有疑問(wèn):為什么要這么嵌套定義?把函數(shù)都定義在全局不好嗎?
其實(shí),大多數(shù)情況下我們都會(huì)將函數(shù)定義在全局,不會(huì)嵌套著定義函數(shù)。不過(guò),當(dāng)我們定義一個(gè)函數(shù)去實(shí)現(xiàn)某功能,想要將內(nèi)部功能拆分成N個(gè)函數(shù),又擔(dān)心這個(gè)N個(gè)函數(shù)放在全局會(huì)與其他函數(shù)名沖突時(shí)(尤其多人協(xié)同開(kāi)發(fā))可以選擇使用函數(shù)的嵌套。
1、基于內(nèi)存和執(zhí)行過(guò)程分析作用域。
直接看2個(gè)例子,基本就可以理解了,我這邊并沒(méi)有去運(yùn)行,可以的話,大家最好去親自運(yùn)行,看看與自己理解的是不是一樣。
這邊要注意一個(gè)問(wèn)題:外部函數(shù)在return返回內(nèi)部的函數(shù)名時(shí),并沒(méi)有執(zhí)行函數(shù),而是只返回函數(shù)名,僅此而已??聪旅娴睦樱?/p>
name = "dack"
def run():
name = "dack_deng"
def inner():
print(name)
return inner # run函數(shù)僅僅只是返回的一個(gè)函數(shù)名而已
v1 = run() # 現(xiàn)在v1是執(zhí)行了run()函數(shù),返回inner這個(gè)函數(shù)名,即v1==inner
v1() # v1()==inner(),開(kāi)始執(zhí)行函數(shù)inner,輸出“dack_deng”
v2 = run() # 同理
v2()
3、總結(jié):
三句話搞定作用域:
作用域鏈
。(函數(shù)嵌套)閉包,簡(jiǎn)而言之就是將數(shù)據(jù)封裝在一個(gè)包(區(qū)域)中,使用時(shí)再去里面取。(本質(zhì)上閉包是基于函數(shù)嵌套搞出來(lái)一個(gè)特殊的嵌套)
也是看下面2個(gè)例子:
"""例子1"""
def task(arg):
def inner():
print(arg)
return inner # 函數(shù)僅僅只是返回inner函數(shù)名而已
v1 = task(11) # v1 == inner,此時(shí)agr == 11
v2 = task(22) # v2 == inner,此時(shí)agr == 22
v3 = task(33) # v3 == inner,此時(shí)agr == 33
v1() # 調(diào)用函數(shù)inner(),此時(shí)agr == 11,輸出:11
v2() # 調(diào)用函數(shù)inner(),此時(shí)agr == 22,輸出:22
v3() # 調(diào)用函數(shù)inner(),此時(shí)agr == 33,輸出:33
"""例子2"""
def task(arg):
def inner():
print(arg)
return inner
inner_func_list = []
for val in [11,22,33]:
inner_func_list.append( task(val) ) # 執(zhí)行玩for循環(huán)之后:inner_func_list == [inner, inner, inner]
inner_func_list[0]() # 11 # 函數(shù)調(diào)用inner_func_list中的第一個(gè)函數(shù),并且第一函數(shù)調(diào)用時(shí)arg==11,輸出:11
inner_func_list[1]() # 22 # 函數(shù)調(diào)用inner_func_list中的第一個(gè)函數(shù),并且第一函數(shù)調(diào)用時(shí)arg==22,輸出:22
inner_func_list[2]() # 33 # 函數(shù)調(diào)用inner_func_list中的第一個(gè)函數(shù),并且第一函數(shù)調(diào)用時(shí)arg==33,輸出:33
通過(guò)上面的函數(shù)嵌套和閉包的理解,接下來(lái)講裝飾器基本更好理解了。
裝飾器就是在不修改原函數(shù)內(nèi)容的前提下,通過(guò)@函數(shù)可以實(shí)現(xiàn)在函數(shù)前后自定義執(zhí)行一些功能(批量操作會(huì)更有意義),類(lèi)似于自動(dòng)化測(cè)試中的前置和后置,這就更好理解了它是個(gè)啥東西了。
話不多說(shuō),先將裝飾器之前,先上裝飾器的萬(wàn)能公式,所有的裝飾器都是基于這個(gè)公式去套用的。
"""裝飾器的萬(wàn)能公式"""
def outer(origin):
def inner(*args, **kwargs): # 這里的*args和**kwargs不固定,可根據(jù)自己的項(xiàng)目中去指定
# 執(zhí)行前
res = origin(*args, **kwargs) #調(diào)用原來(lái)的func函數(shù)
# 執(zhí)行后
return res
return inner
@outer # 一定要記住這個(gè):func = outer(func) ==========> func == innner
def func():
pass
func()
接下來(lái)直接上題:
"""1, 請(qǐng)為以下所有函數(shù)編寫(xiě)一個(gè)裝飾器,添加上裝飾器后可以實(shí)現(xiàn),執(zhí)行func時(shí),先執(zhí)行func函數(shù)內(nèi)部代碼
再輸出”after“"""
def outer(origin):
def inner(*args, **kwargs):
res = origin(*args, **kwargs)
print("after")
return res
return inner
@outer
def func(a1):
print("func")
return a1 + "哈哈"
@outer
def base(a1, a2):
print("base")
return a1 + a2 + "呵呵"
@outer
def foo(a1, a2, a3, a4):
print("foo")
return a1 + a2 + a3 + a4 + "嘻嘻"
func("1")
base("1", "2")
foo("1","2","3","4")
"""2, 編寫(xiě)裝飾器,添加上裝飾器后實(shí)現(xiàn):將被裝飾的函數(shù)執(zhí)行5次,將每次執(zhí)行函數(shù)的結(jié)果按照順序放到
列表中,最終返回列表"""
import random
def outer(origin):
def inner(*args, **kwargs):
print("before")
res = origin(*args, **kwargs)
print("after")
return res
return inner
@outer
def func():
return random.randint(1, 4)
list = []
for i in range(5):
print(i)
result = func()
list.append(result)# 內(nèi)部自動(dòng)執(zhí)行5次,并將每次執(zhí)行的結(jié)果追加到列表最終返回給result
print(list)
"""3, 編寫(xiě)函數(shù)裝飾器,添加上裝飾器后可以實(shí)現(xiàn):檢查文件所在路徑(文件夾)是否存在,如果不存在
自動(dòng)創(chuàng)建文件夾(保證寫(xiě)入文件不報(bào)錯(cuò))"""
import os
file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'usr', 'bin', 'xxx')
def outer(origin):
def inner(*args, **kwargs):
# 函數(shù)執(zhí)行前的操作
if not os.path.exists(file_path):
os.makedirs(file_path)
res = origin(*args, **kwargs) # 執(zhí)行被裝飾的原函數(shù)
# 執(zhí)行完函數(shù)的下一步操作
return res
return inner
@outer
def writer_user_info(path):
with open(path, mode='w', encoding='utf-8') as file_obj:
for i in range(10):
file_obj.write("dack\n")
writer_user_info('usr/bin/xxx/xxx.txt')
總結(jié):
我的那種寫(xiě)法就稱(chēng)為裝飾器。
實(shí)現(xiàn)原理:基于@語(yǔ)法和函數(shù)閉包,將原函數(shù)封裝在閉包中,然后將函數(shù)賦值為一個(gè)新的函數(shù)(內(nèi)層函數(shù)),執(zhí)行函數(shù)時(shí)再在內(nèi)層函數(shù)中執(zhí)行閉包中的原函數(shù)。
實(shí)現(xiàn)效果:可以在不改變?cè)瘮?shù)內(nèi)部代碼 和 調(diào)用方式的前提下,實(shí)現(xiàn)在函數(shù)執(zhí)行和執(zhí)行擴(kuò)展功能。
適用場(chǎng)景:多個(gè)函數(shù)系統(tǒng)統(tǒng)一在 執(zhí)行前后自定義一些功能
先看下面這些實(shí)例:
"""示例1"""
def handler():
pass
handler()
print(handler.__name__) # handler
"""示例2"""
def auth(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@auth
def handler():
pass
handler()
print(handler.__name__) # inner
"""示例3"""
import functools
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@auth
def handler():
pass
handler()
print(handler.__name__) # handler
所以,一般情況下大家不用functools也可以實(shí)現(xiàn)裝飾器的基本功能,但后期在項(xiàng)目開(kāi)發(fā)時(shí),不加functools會(huì)出錯(cuò)(因?yàn)閮?nèi)部會(huì)讀取__name__
,且__name__
重名的話就報(bào)錯(cuò)),所以在此大家就要規(guī)范起來(lái)自己的寫(xiě)法。
所以裝飾器的萬(wàn)能公式,我們就可以更新如下所示:
import functools
def outer(func):
@functools.wraps(func)
def inner(*args, **kwargs):
# 函數(shù)執(zhí)行前的操作
res = func(*args, **kwargs) # 執(zhí)行原函數(shù)
# 函數(shù)執(zhí)行后的操作
return res
return inner
@outer # func = outer(func)
func()
pass
func()
關(guān)于裝飾器的知識(shí),到此全部講完啦,希望對(duì)大家有所幫助! 如果有不對(duì)的地方,歡迎各位小伙伴指出,感謝!