Python裝飾器詳細(xì)介紹
裝飾器
一、介紹
- 器:代表函數(shù)的意思。裝飾器本質(zhì)就是是函數(shù)
- 功能:裝飾其他函數(shù),就是為其他函數(shù)添加附加功能
- 被裝飾函數(shù)感受不到裝飾器的存在
- 原則:
不能修改被裝飾的函數(shù)的源代碼(比如線上環(huán)境)
不能修改被裝飾的函數(shù)的調(diào)用方式
- 實(shí)現(xiàn)裝飾器知識(shí)儲(chǔ)備:
函數(shù)即是“變量”
高階函數(shù)
嵌套函數(shù)
高階函數(shù)+嵌套函數(shù)=>裝飾器
二、通過(guò)高階函數(shù)+嵌套函數(shù)==>實(shí)現(xiàn)裝飾器
先分析以下兩段代碼能不能運(yùn)行?
def foo(): print("in the foo") bar() def bar(): print("in the bar") foo()
def foo(): print("in the foo") bar() foo() def bar(): print("in the bar")
第二段代碼報(bào)錯(cuò):
NameError: name 'bar' is not defined
1、變量知識(shí)回顧
定義變量:
如:定義變量:x=1,會(huì)在內(nèi)存中找塊內(nèi)存空間把“1”存進(jìn)去,把“1”的內(nèi)存地址給x
前面提到:函數(shù)即變量
# 定義函數(shù) def test(): pass # 就相當(dāng)于把函數(shù)體賦值給test變量 test = '函數(shù)體' # 函數(shù)體就是一堆字符串而已 # 只不過(guò)函數(shù)調(diào)用要加上小括號(hào)調(diào)用 test()
python內(nèi)存回收機(jī)制,是解釋器做的。解釋器到底怎么去回收這個(gè)變量?
python解釋器當(dāng)中有種概念叫做引用計(jì)數(shù)。什么叫引用計(jì)數(shù)呢?
比如:定義x=1,之后又定義了y=1或y=x,實(shí)際上又把內(nèi)存空間“1”的內(nèi)存地址賦值給y
這里x代表一次引用,y代表一次引用。加起來(lái)兩次引用。
python什么時(shí)候會(huì)把“1”這個(gè)內(nèi)存空間清空呢?會(huì)回收內(nèi)存呢?
當(dāng)x這個(gè)變量沒(méi)有了,y這個(gè)變量也沒(méi)有了,便會(huì)把“1”這個(gè)內(nèi)存空間清掉
del x # 刪的只是變量名,內(nèi)存中的值是解釋器回收
匿名函數(shù)
lambda x:x*x
匿名函數(shù)沒(méi)有函數(shù)名,沒(méi)有引用,所以會(huì)被垃圾回收機(jī)制立馬回收掉。
所以匿名函數(shù)要賦值給變量,把函數(shù)體賦值給變量名
calc = lambda x:x*x print(calc(4))
現(xiàn)在可以再理解下最開(kāi)始兩段代碼能不能運(yùn)行的原因。
2、高階函數(shù)(裝飾器前奏)
什么叫高階函數(shù)呢:
- 把一個(gè)函數(shù)名當(dāng)做形實(shí)傳給另外一個(gè)函數(shù)
- 返回值中包含函數(shù)名
def f1(): print("in the func1") def test1(func): print(func) test1(f1)
運(yùn)行結(jié)果(打印內(nèi)存地址)
<function func1 at 0x000002805DE12378>
如下代碼,能不能運(yùn)行:
def f1(): print("in the func1") def test1(func): print(func) func() test1(f1)
函數(shù)即變量,像“x=1,y=x”,同樣f是一個(gè)是一個(gè)函數(shù),可不可以像一個(gè)變量一樣來(lái)回賦值呢?
import time def func1(): print("in the func1") time.sleep(1) def test1(func): start_time = time.time() func() stop_time = time.time() print("the func run time is %s" %(stop_time-start_time)) test1(func1)
到這里,貌似實(shí)現(xiàn)了裝飾函數(shù)的功能。
看上面裝飾器的原則:
這里:沒(méi)有修改func1的源代碼,但是調(diào)用方式改變了。現(xiàn)在是test1(func1),之前是func1()
現(xiàn)在能做到哪一點(diǎn)呢?
把一個(gè)函數(shù)名當(dāng)做實(shí)參傳給另外一個(gè)函數(shù)(不修改被裝飾的函數(shù)源代碼的情況下為其添加功能)
2) 下面用第二個(gè)條件(返回值中包含函數(shù)名),做另外一個(gè)高階函數(shù)
import time def func2(): time.sleep(1) print("in the func2") def test2(func): print(func) return(func) print(test2(func2))
運(yùn)行結(jié)果:
<function func2 at 0x00000162F3672378>
<function func2 at 0x00000162F3672378>
把函數(shù)內(nèi)存地址都打印出來(lái)了,看到這么多內(nèi)存地址,有什么想法?
加上小括號(hào)就能運(yùn)行。
上面代碼“test2(func2())”和“test2(func2)”有什么區(qū)別?加上小括號(hào)是函數(shù)返回結(jié)果,不加是函數(shù)內(nèi)存地址。所以加上小括號(hào)就不符合高階函數(shù)定義了。
既然以后有了函數(shù)的內(nèi)存地址,是不是可以賦值給其他變量?下面
import time def func2(): print("in the func2") time.sleep(1) def test2(func): print(func) return(func) t = test2(func2) print(t) t()
好像還沒(méi)什么用,怎么讓他有用呢?
把test2(func2)賦值給func2
import time def func2(): print("in the func2") time.sleep(1) def test2(func): print(func) return(func) func2 = (test2(func2)) func2()
這就是高階函數(shù)的第二個(gè)好處:返回值中包含函數(shù)名(不修改函數(shù)的調(diào)用方式)
3、嵌套函數(shù)(裝飾器前戲)
嵌套函數(shù):在一個(gè)函數(shù)體內(nèi),用def去聲明一個(gè)函數(shù)
def foo(): print("in the foo") def bar(): print("in the bar") bar() foo()
看一下下面的代碼是不是嵌套:
def foo(): print("in the foo") def bar(): foo() bar()
注意函數(shù)嵌套和函數(shù)調(diào)用區(qū)別
局部作用域和全局作用域的訪問(wèn)順序:
x = 0 def grandpa(): # x = 1 def dad(): x = 2 def son(): x = 3 print(x) son() dad() grandpa()
三、裝飾器
1、裝飾器
前面鋪墊了那么多,現(xiàn)在開(kāi)講正題:裝飾器
先用高階函數(shù)實(shí)現(xiàn)給函數(shù)不修改源代碼的情況下添加功能
import time def deco(func): start_time = time.time() func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) def test1(): time.sleep(1) print("in the test1") def test2(): time.sleep(1) print("in the test2") deco(test1) deco(test2)
按照上面說(shuō)的,如何實(shí)現(xiàn)不改變調(diào)用方式?直接“test1 = deco(test1)”和“test2 = deco(test2)”嗎?
別忘記了,第二種方式,高階函數(shù)要加上return,如下
import time def deco(func): start_time = time.time() return func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) def test1(): time.sleep(1) print("in the test1") def test2(): time.sleep(1) print("in the test2") test1 = deco(test1) test2 = deco(test2) deco(test1) deco(test2)
雖然沒(méi)有修改源代碼和調(diào)用方式,但是函數(shù)加上return,函數(shù)就結(jié)束了,然并卵。怎么實(shí)現(xiàn)呢?
前面一直在用高階函數(shù),還沒(méi)有用嵌套函數(shù),加上嵌套函數(shù)能不能實(shí)現(xiàn)呢?看一下
import time def timer(func): # timer(test1) func=test1 def deco(): start_time = time.time() func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回deco的內(nèi)存地址 def test1(): time.sleep(1) print("in the test1") def test2(): time.sleep(1) print("in the test2") print(timer(test1)) # 可見(jiàn):返回deco的內(nèi)存地址 test1 = timer(test1) test1() timer(test2)()
到此,完成實(shí)現(xiàn)了裝飾器的功能。但是還是有點(diǎn)麻煩,如何能不要“test1 = timer(test1)”,
python解釋器提供了語(yǔ)法糖“@”符合,給哪個(gè)函數(shù)新增功能,就加在哪個(gè)函數(shù)頭部
import time def timer(func): # timer(test1) func=test1 def deco(): start_time = time.time() func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回deco的內(nèi)存地址 @timer def test1(): time.sleep(1) print("in the test1") @timer def test2(): time.sleep(1) print("in the test2") test1() test2()
2、有參裝飾器
前面實(shí)現(xiàn)了裝飾器的功能,但是如果函數(shù)有參數(shù),能不能也能運(yùn)行呢
import time def timer(func): # timer(test1) func=test1 def deco(): start_time = time.time() func() stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回deco的內(nèi)存地址 @timer def test1(): time.sleep(1) print("in the test1") @timer def test2(name): time.sleep(1) print("in the test2",name) test1() test2()
報(bào)錯(cuò):丟失參數(shù)
TypeError: test2() missing 1 required positional argument: 'name'
@timer 相當(dāng)于 test2=timer(test2) =deco
test2() 相當(dāng)于運(yùn)行deco(),所以沒(méi)指定參數(shù),報(bào)錯(cuò)。
如何傳參數(shù)呢?為了適應(yīng)各種不同參數(shù)的函數(shù)
import time def timer(func): # timer(test1) func=test1 def deco(*args,**kwargs): start_time = time.time() func(*args,**kwargs) stop_time = time.time() print("the func tun time is %s" %(stop_time-start_time)) return deco # 返回deco的內(nèi)存地址 @timer def test1(): time.sleep(1) print("in the test1") @timer def test2(name): time.sleep(1) print("in the test2",name) test1() test2("fgf")
3、終極裝飾器
注意,上面的例子中還沒(méi)有涉及返回值,看下面的例子可以體會(huì)一下
假設(shè):公司網(wǎng)站需要驗(yàn)證登錄,有不同的驗(yàn)證方式:本地認(rèn)證、LDAP認(rèn)證等
#/usr/bin/env python # -*- coding: UTF-8 -*- import time user,passwd = 'fgf','abc123' def auth(auth_type): print("auth func:",auth_type) def outer_wrapper(func): def wrapper(*args, **kwargs): print("wrapper func args:", *args, **kwargs) if auth_type == "local": username = input("Username:").strip() password = input("Password:").strip() if user == username and passwd == password: print("\033[32;1mUser has passed authentication\033[0m") res = func(*args, **kwargs) # from home print("---after authenticaion ") return res else: exit("\033[31;1mInvalid username or password\033[0m") elif auth_type == "ldap": print("搞毛線ldap,不會(huì)。。。。") return wrapper return outer_wrapper def index(): print("welcome to index page") @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type="ldap") def bbs(): print("welcome to bbs page") index() print(home()) #wrapper() bbs()
到此這篇關(guān)于Python裝飾器詳細(xì)講解的文章就介紹到這了,更多相關(guān)Python裝飾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解python的幾種標(biāo)準(zhǔn)輸出重定向方式
這篇文章是基于Python2.7版本,介紹常見(jiàn)的幾種標(biāo)準(zhǔn)輸出(stdout)重定向方式。顯然,這些方式也適用于標(biāo)準(zhǔn)錯(cuò)誤重定向。學(xué)習(xí)python的小伙伴們可以參考借鑒。2016-08-08Python基于identicon庫(kù)創(chuàng)建類似Github上用的頭像功能
這篇文章主要介紹了Python基于identicon庫(kù)創(chuàng)建類似Github上用的頭像功能,結(jié)合具體實(shí)例形式分析了identicon庫(kù)操作圖形的具體步驟與相關(guān)使用技巧,需要的朋友可以參考下2017-09-09Python標(biāo)準(zhǔn)模塊--ContextManager上下文管理器的具體用法
本篇文章主要介紹了Python標(biāo)準(zhǔn)模塊--ContextManager的具體用法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11Python實(shí)現(xiàn)修改圖片分辨率(附代碼)
這篇文章主要介紹了Python通過(guò)ffmpeg實(shí)現(xiàn)修改圖片分辨率,文中的代碼介紹詳細(xì),對(duì)我們的工作或?qū)W習(xí)有一定的價(jià)值,感興趣的小伙伴可以學(xué)習(xí)一下2021-12-12淺談selenium如何應(yīng)對(duì)網(wǎng)頁(yè)內(nèi)容需要鼠標(biāo)滾動(dòng)加載的問(wèn)題
這篇文章主要介紹了淺談selenium如何應(yīng)對(duì)網(wǎng)頁(yè)內(nèi)容需要鼠標(biāo)滾動(dòng)加載的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03利用Python將時(shí)間或時(shí)間間隔轉(zhuǎn)為ISO 8601格式方法示例
國(guó)際標(biāo)準(zhǔn)化組織的國(guó)際標(biāo)準(zhǔn)ISO8601是日期和時(shí)間的表示方法,全稱為《數(shù)據(jù)存儲(chǔ)和交換形式·信息交換·日期和時(shí)間的表示方法》,下面這篇文章主要給大家介紹了關(guān)于利用Python將時(shí)間或時(shí)間間隔轉(zhuǎn)為ISO 8601格式的相關(guān)資料,需要的朋友可以參考下。2017-09-09使用matplotlib繪制并排柱狀圖的實(shí)戰(zhàn)案例
堆積柱狀圖有堆積柱狀圖的好處,比如說(shuō)我們可以很方便地看到多分類總和的趨勢(shì),下面這篇文章主要給大家介紹了關(guān)于使用matplotlib繪制并排柱狀圖的相關(guān)資料,需要的朋友可以參考下2022-07-07