Python裝飾器實體講解(二)
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引數 -
如果還不明白...盡力了
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543397.html
標籤:其他
