日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

Python装饰器学习笔记

發布時間:2024/4/18 python 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python装饰器学习笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Python裝飾器

文章目錄

  • Python裝飾器
    • 基本概念
    • 從零開始的逐步分析
    • 修飾后的問題
    • 向被包裝后的函數傳遞參數
    • 使用場景:stdout日志
    • 接受參數的裝飾器
    • 作為一個類的裝飾器
    • 總結

學習資料:

  • Python 函數裝飾器
  • 裝飾器類
  • Python __call__()方法(詳解版)

基本概念

先建立一個觀點:
函數本質上是一個對象,可以賦值給其他變量
加上()后就變成執行函數本身,并獲得其返回值

修改其他函數功能的函數,即是裝飾器

因為它要修改其他函數,所以裝飾器本身應該返回一個函數
從而將這個修改后的函數賦值給原函數,以達到修改原函數功能的目的

從零開始的逐步分析

首先是一個原型理解
主要理解函數本身是一個對象
可以賦值給其他變量,或是本身作為參數

def deco_func(func):print("doing", func.__name__)func()def hi():print("hi")deco_func(hi)

上述代碼的函數hi不能接受參數
即如果變成這樣子,則會報錯:

def deco_func(func):print("doing", func.__name__)func()def hi(name):print("hi", name)deco_func(hi("Archeri"))

輸出:

hi Archeri Traceback (most recent call last):File "D:\python_work\guide\deco.py", line 8, in <module>deco_func(hi("Archeri"))File "D:\python_work\guide\deco.py", line 2, in deco_funcprint("doing", func.__name__) AttributeError: 'NoneType' object has no attribute '__name__'. Did you mean: '__ne__'?

上面的代碼,會先執行hi("Archeri"),然后執行deco_func函數
因為hi函數加上了()因此變成了執行函數本身,并取得其返回值
由于hi函數沒有任何返回值,或者說返回NoneType
因此報錯信息最后面提醒說
'NoneType' object has no attribute '__name__'.
合情合理

為了可以給內函數傳遞參數,則可能需要在外函數里定義一個新的函數去返回

簡單的剖析代碼:

# 裝飾器,就是一個函數,接收要修飾的原函數 # 這個deco_func整體就是一個裝飾器 def deco_func(func):# 因為要返回一個函數,所以在函數里面定義一個新的函數來返回# 這個函數用于在執行原函數前后,執行一些操作def wrap_func():print("doing", func.__name__)func()# 返回“修改”了原函數功能的,內部新定義的函數return wrap_func# 原函數 def hi():print("hi")# 執行原函數,看看原功能 hi() #outputs: hi# ↓↓↓裝飾器起作用的原理就在這里↓↓↓ # 裝飾好原函數之后,將修改后的函數返回給原函數 hi = deco_func(hi) #now hi is wrapped by wrap_func()# 檢驗裝飾結果 hi() #outputs:doing hi # hi

使用“@”的等效裝飾效果:

# 裝飾器,就是一個函數,接收要修飾的原函數 # 這個deco_func整體就是一個裝飾器 def deco_func(func):# 因為要返回一個函數,所以在函數里面定義一個新的函數來返回# 這個函數用于在執行原函數前后,執行一些操作def wrap_func():print("doing", func.__name__)func()# 返回“修改”了原函數功能的,內部新定義的函數return wrap_func# 原函數,使用“@”裝飾 @deco_func def hi():print("hi")# 檢驗裝飾結果 hi() #outputs:doing hi # hi# 函數定義時使用“@”裝飾 # 實際上就是下面這個展示裝飾原理的語句: # hi = deco_func(hi)

以上就是裝飾器最基本的理解


修飾后的問題

但是上面使用“@”修飾函數之后
print(hi.__name__)會輸出wrap_func
即hi函數的一些屬性變了
是因為此時hi函數相當于承接了deco_func的返回值,相當于是wrap_func函數
因此hi此時更像是一個“變量”而不是“函數”
hi.__name__輸出的指代返回的wrap_func

Python為上面的問題提供了一個原生又簡單的解決方法:

from functools import wrapsdef deco_func(func):@wraps(func)def wrap_func():print("doing", func.__name__)func()return wrap_func

即導入wraps,然后在裝飾器返回的函數前面用它來裝飾即可
@wraps(func)

@wrap接受一個函數來進行裝飾,并加入了復制函數名稱、注釋文檔、參數列表等等的功能。
這可以讓我們在裝飾器里面訪問在裝飾之前的函數的屬性。


向被包裝后的函數傳遞參數

下面為了自己方便理解,作以下定義:
deco_func函數:裝飾器
wrap_func函數:裝飾器的返回結果,稱為包裝函數

若想給被裝飾函數傳入參數
則在包裝函數原型中添加所有可變參數,并且在傳給內部調用的被包裝函數

from functools import wrapsdef deco_func(func):# 下面只是在定義函數,不執行其中的過程@wraps(func)def wrap_func(*args, **kw):print("doing", func.__name__)func(*args, **kw)# 最后返回定義好的函數,沒有執行它return wrap_func@deco_func def hi(name):print("hi", name)hi("Archeri") #output: doing hi # hi Archeri

使用場景:stdout日志

from functools import wrapsdef logit(func):@wraps(func)def wrap_func(*args, **kw):print("doing", func.__name__)return func(*args, **kw)return wrap_func@logit def my_add(x, y):return x + y@logit def my_dec(x, y):return x - yprint(my_add(1, 1)) print(my_dec(1, 1))

output:

doing my_add 2 doing my_dec 0

接受參數的裝飾器

上述裝飾器雖然能裝飾不同的函數
但是“裝飾”這一行為本身是一樣的
就好像一種啤酒,能裝在任何杯子里
但裝的量都是一樣的
不能根據具體杯子的大小去變化裝入的多少

因此可以給裝飾器加上除被裝飾函數之外的參數
來使裝飾器的裝飾行為變得可變、靈活

像是上面的日志場景,只能規定日志輸出在stdout
下面就給裝飾器添加參數,能夠選擇輸出位置

一開始我是這樣寫裝飾器的

def logit(func, logfile):@wraps(func)def wrap_func(*args, **kw):with open(logfile, 'a') as log_obj:log_obj.write("doing " + func.__name__)return func(*args, **kw)return wrap_func

想著直接在裝飾器原型里加上除被裝飾函數外的另一個參數
但是當使用@去裝飾my_add函數時,傳參遇到了問題

一開始@logit("addlog"),但是提示logit() missing 1 required positional argument: 'logfile',說明這個日志文件參數給到了func

然后試試@logit(logfile="addlog"),但是又提示缺失參數func

好嘛,@logit(func=my_add, logfile="addlog"),就說我my_add未定義了

說明上面那個帶參數的裝飾器的寫法是有問題的


之后看了看正確的寫法
下面就是我的分析和猜測

上面的裝飾器,用@裝飾時使用了()
是不是意味著實際的裝飾器變成了logit函數執行后的返回結果
即是wrap_func?
也就是說@logit()=@wrap_func?

看了看正確寫法,為此又加多了一層函數去封裝?

from functools import wraps# 最外層接受裝飾器的參數 def logit(logfile):# 因為使用“@”裝飾時要加圓括號傳參,所以最外層應該返回真正的裝飾器def log_deco(func):# 真正的裝飾器,返回裝飾后的原函數@wraps(func)def wrap_func(*args, **kw):with open(logfile, 'a') as log_obj:log_obj.write("doing "+func.__name__+'\n')return func(*args, **kw)return wrap_funcreturn log_deco

測試:

@logit("addlog") def my_add(x, y):return x + y@logit("declog") def my_dec(x, y):return x - yprint(my_add(1, 1)) print(my_dec(1, 1))

結果輸出“2”、“0”
然后也各自生成了正確的日志文件,寫入了正確的內容

作為一個類的裝飾器

上面涉及到的裝飾器,本質上都是一個函數(普通裝飾器)
而實際上裝飾器本身也可以是一個類(裝飾器類)

例如上面的不帶參數的裝飾器,可以這樣把它寫成一個類:

class logit:# 初始化工作def __init__(self, func):self.func = func# __call__方法使得類可像函數那樣去加上()調用# 類似于之前的warp函數def __call__(self, *args, **kw):print("doing", self.func.__name__)return self.func(*args, **kw)

普通的裝飾方法:

my_add = logit(my_add) # 之后的my_add變成了一個logit類的實例 print(my_add(1,1)) #output: doing my_add # 2

上面等價于print(logit(my_add)(1, 1))
輸出也是一樣的

使用@的裝飾方法:

@logit def my_add(x, y):return x + yprint(my_add(1,1)) # 輸出同上面一致 # 裝飾后的my_add也變成了logit類的一個實例

下面是接受參數的動態裝飾器類的寫法:

from functools import wrapsclass logit:# 因為是在這個類初始化時傳入的參數# 所以應該在__init__方法里準備好裝飾器要用的參數def __init__(self, logfile):self.logfile = logfile# 因為初始化時使用()去傳入裝飾器參數# 后面需要再調用一次才能執行這個__call__# 因此在這里使用被裝飾的函數def __call__(self, func):# 返回被裝飾后的原函數# 只定義,不執行@wraps(func)def wrap_func(*args, **kw):with open(self.logfile, "a") as log_obj:log_obj.write("doing "+func.__name__+'\n')return func(*args, **kw)return wrap_func

普通的裝飾方法:

def my_add(x, y):return x + y my_add = logit("addlog")(my_add) print(my_add(1, 1)) # 輸出2,生成日志文件

使用@的裝飾方法:

@logit("addlog") def my_add(x, y):return x + y print(my_add(1, 1)) # 輸出2,生成日志文件

總結

裝飾器類比普通裝飾器具有更好的可讀性

聽說還要知道什么閉包的概念
會更好理解一點
先放著,會用就行

總結

以上是生活随笔為你收集整理的Python装饰器学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。