Python裝飾器實例講解(二) Python裝飾器實例講解(一) 你最好去看下第一篇,雖然也不是緊密的鏈接在一起 參考B站碼農高天的視頻,大家喜歡看視頻可以跳轉忽略本文:https://www.bilibili.com/video/BV19U4y1d79C 一鍵三連哦 本文的知識點主要是 類裝 ...
Python裝飾器實例講解(二)
你最好去看下第一篇,雖然也不是緊密的鏈接在一起
參考B站碼農高天的視頻,大家喜歡看視頻可以跳轉忽略本文:https://www.bilibili.com/video/BV19U4y1d79C
一鍵三連哦
本文的知識點主要是 類裝飾器
裝飾器的本質(up主說的萬能公式)
案例
-
代碼
def count_time(func): def wrapper(*args,**kwargs): from time import time start_time = time() result = func(*args,**kwargs) end_time = time() print(f'統計花了{end_time-start_time}時間') return result return wrapper
-
改造為類裝飾器(註意對比)
- 你得知道基礎的python的面向對象的知識
- 一些類的魔術方法如__init__和__call__
class CountTime: def __init__(self,function_name): # 類沒傳參一說,但實例化是可以傳參的,類比 def count_time(func): self.function_name = function_name def __call__(self, *args, **kwargs): # 類實例的(),像函數的call , ==>def wrapper(*args,**kwargs): from time import time start_time = time() result = self.function_name(*args,**kwargs) # 也就改了這裡,其他都一樣 end_time = time() print(f'統計花了{end_time-start_time}時間') return result
-
完整的代碼
def is_prime(x): if x == 2 : return True elif x % 2 == 0 or x == 1 : return False for i in range(3, int(x ** 0.5) + 1, 2): if x % i == 0: return False return True class CountTime: ... # 就不重覆上面了 @CountTime # 類是一個裝飾器 def get_prime_nums(start,end): prime_nums = 0 for num in range(start,end): if is_prime(num): prime_nums = prime_nums + 1 return prime_nums print(get_prime_nums(2,50000)) # 效果是一樣的
碼農高天說
我把up主的一些話摘錄一些寫到這裡,輔助大家理解
- 裝飾器decorator:是一個輸入是函數,輸出也是函數的函數(看講解一中的裝飾器)
- 類裝飾器 class decorator,up主說有一定的歧義
- 可以當做裝飾器的類(裝飾器本身)
- 可以裝飾類的裝飾器(裝飾器要裝飾的對象)
- 裝飾器本身既可以是函數也可以是類,裝飾的對象同樣可以是函數或者類
- 背這些理論沒有意義,關鍵要弄懂背後的原理
- __call__可以讓類的實例當做函數用(就是callable)
萬能公式
-
裝飾器語法糖背後
class CountTime: ...# 同上 @CountTime def add(a,b): # 就用碼農的demo函數 return a+b print(add(1,2))
-
@CountTime等價於,所謂的萬能公式咯
add = CountTime(add)
-
print(add(1,2))已經不再是使用的原始的add了,用的是新的add
print(add(1,2)) 等價於 print(CountTime(add)(1,2))
-
也就是說
# @CountTime # 去掉裝飾器,你就是定義了一個簡單的函數 # # add = CountTime(add) # 函數名被重定義了 相當於這樣 def add(a,b): return a+b print(CountTime(add)(1,2))
-
你還可以這樣
def add(a,b): return a+b new_add = CountTime(add) print(new_add(1,2))
-
是的,被裝飾過的函數已經不再是原來的函數了,它總是會先去執行裝飾器(CountTime(add))
-
總結:
- 在一個函數上做裝飾器,等價於裝飾器調用這個函數
- 在類裝飾器的這個例子中,add從一個函數變成了一個類的實例(type看下即可)
改造,有參數的裝飾器
-
我們看到過很多的裝飾是有參數的,這是怎麼實現的呢?
-
比如你想要輸出的信息可以調整其首碼
計時: 0.46秒 或者 用時: 0.46秒
-
你希望是這樣裝飾和調用的
@CountTime(prefix='用時:') def add(a,b): return a+b print(add(1,2))
-
那咋實現呢?
-
回到萬能公式:
@CountTime(prefix='用時:') def add(a,b): ... # 等價於 add = CountTime(add) # 那麼 @CountTime(prefix='用時:') def add(a,b): ... # 等價於 add = CountTime(prefix='用時:')(add)
-
CountTime這個類能CountTime(prefix='用時:'),就是實例化做到的,所以...類的init方法要改一下,不再是傳參function_name了,而是傳你的prefix,像這樣
class CountTimeV2: def __init__(self,prefix='用時:'): self.prefix = prefix
-
但現在還不能繼續,add = CountTime(prefix='用時:')(add)中你(add)還要處理,前面是init做的,()就是callable做的,裡面的參數是add,也就是函數的名字,所以你的call也要改造,像這樣嗎?
def __call__(self, function_name): from time import time start_time = time() result = function_name(*args,**kwargs) end_time = time() print(f'統計花了{end_time-start_time}時間') return result
-
不對的,光這樣改造不夠的,因為你這個function_name(*args,**kwargs)在IDE中就會報錯,哪裡來的呢?沒有定義。
-
回想講解一中,函數裝飾器裡層,還有一個函數,此處就可以參考
class CountTimeV2: def __init__(self, prefix='用時:'): self.prefix = prefix def __call__(self, function_name): def wrapper(*args, **kwargs): # 加了個函數 , 包裹一層 from time import time start_time = time() result = function_name(*args, **kwargs) # 這樣就可以用參數了 end_time = time() print(f'{self.prefix}{end_time - start_time}') # 用之前的定義 return result return wrapper @CountTimeV2(prefix='耗時:') # 可以改為用時、計時等等 def add(a, b): return a + b print(add(1, 2))
前面談的是類是一個裝飾器,裝了一個函數
下麵談的是函數是一個裝飾器,裝飾一個類
類的裝飾器
-
現在有這麼一個類
class Person: pass wuxianfeng = Person() print(wuxianfeng) # <__main__.Person object at 0x000002361C15A460>
-
你學過python可以這樣修改
class Person: def __str__(self): return f"{self.__class__.__name__}" wuxianfeng = Person() print(wuxianfeng) # Person
-
但如果有很多的類都要如此呢?
-
可以寫個裝飾器,來裝飾這些類唄
-
怎麼寫?回想剛纔你學到的知識,萬能公式!
def show_classname(): # 先不寫參數 pass # 先不寫內容 @show_classname class Person: pass Person = show_classname(Person) wuxianfeng = Person() print(wuxianfeng)
-
你現在要寫一個函數,名字隨意,如show_classname
-
你肯定要裝飾在類上
@show_classname class Person: pass
-
根據萬能公式,你的Person應該變了
Person = show_classname(Person) # 從上面這段代碼,你要能分析出以下內容 # 1. show_classname應該有個參數,傳參是個類名 # 2. 因為可以Person = ,所以show_classname有個返回值
-
對於使用者而言,應該沒有任何操作上的差異
wuxianfeng = Person() # 從上面這段代碼,你要能分析出以下內容 # 1. Person已經被你改變了 # 2. Person()==>show_classname(Person)(),所以show_classname這個函數的返回值還是一個類 print(wuxianfeng)
-
分析完了,函數體部分是有點不好理解的
def show_classname(class_name): def __str__(self): return self.__class__.__name__ class_name.__str__ = __str__ return class_name @show_classname class Person: pass Person = show_classname(Person) wuxianfeng = Person() print(wuxianfeng)
-
看著這個結果,我們來解釋下(也許你會更好理解)
1. show_classname(Person) 返回仍然是Person 2. 但這個時候的Person被改變了一點(你要做的不就是如此嗎?) 3. 原來你是這樣寫的 class Person: def __str__(self): return f"{self.__class__.__name__}" 看看現在的寫法 def __str__(self): return self.__class__.__name__ class_name.__str__ = __str__ # 前面的class_name.__str__ 是類自己的函數(本段解釋的line 5) # 後面的= __str__ ,是line8的函數 # 是的,函數可以被重新賦值,函數是一等對象,
-
如果還不明白...儘力了
-
帶參數的類的裝飾器
碼農高天並沒有給出示例代碼
當然如果你真懂了前面的"改造,有參數的裝飾器",也很簡單
-
直接上代碼
def show_classname(info='類名:'): def wrapper(class_name): def __str__(self): return info+ self.__class__.__name__ class_name.__str__ = __str__ return class_name return wrapper @show_classname('類的名字是:') # class Person: pass wuxianfeng = Person() print(wuxianfeng)
-
預設值就是='類名:',怎麼用呢
@show_classname() class Human: pass qianyuli = Human() print(qianyuli)
-
註意不能這樣
@show_classname class Human: pass qianyuli = Human() print(qianyuli)
-
提示錯誤
Traceback (most recent call last): File "demo.py", line 21, in <module> qianyuli = Human() TypeError: wrapper() missing 1 required positional argument: 'class_name'
-
提個問題,為何會報錯?
-
如果你無法解釋的通,你應該還沒理解。
-
答案其實還是萬能公式。
@show_classname class Human: pass # 1. 等價於(萬能公式來了) Human = show_classname(Human) # 2. show_classname(Human) 執行這個的時候其實你在做 def show_classname(info='類名:'): ... Human這個東西傳給了info # 你要不信,你改為下麵這樣就知道了;信的話就過 def show_classname(info='類名:'): print('info是啥?',info.name) class Human: name = '女媧' # 3. show_classname(Human)這個的返回是wrapper 但wrapper這個函數是有個參數的,看你的定義def wrapper(class_name): # 4. 定義的時候是感知不到問題的,下麵的報錯行 qianyuli = Human() 其實你是在 Human()=>show_classname(Human)()=>wrapper(),錯了,(看3),你需要一個class_name參數
-
如果還不明白...儘力了