你最好去看下第一篇,雖然也不是緊密的連結在一起
參考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
改造為類裝飾器(注意對比)
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主的一些話摘錄一些寫到這裡,輔助大家理解
裝飾器語法糖背後
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))
總結:
我們看到過很多的裝飾是有引數的,這是怎麼實現的呢?
比如你想要輸出的資訊可以調整其字首
計時: 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引數
如果還不明白...盡力了