Python裝飾器實體講解(一)
多種角度講述這個知識,這是個系列文章
但前后未必有一定的順承關系
部分參考網路
本文以一個小案例引出裝飾器的一些特點,不涉及理論,后面再談
案例
-
寫一個代碼來求一個數是否是質數
def is_prime(x): if x == 2 : return True elif x == 1 or x % 2 == 0 : return False for i in range(3, int(x ** 0.5) + 1, 2): if x % i == 0: return False return True -
寫個代碼來計算某個數值范圍內有多少個質數
def get_prime_nums(): return len(list(filter(is_prime,range(2,50000)))) -
換一下,我們不是要學這個,我們要學裝飾器
def get_prime_nums(): from time import time start_time = time() prime_nums = 0 for num in range(2,50000): if is_prime(num): prime_nums = prime_nums + 1 end_time = time() print(f'統計花了{end_time-start_time}時間') print(f'一共有{prime_nums}個質數') get_prime_nums() # 統計花了0.025316476821899414時間 # 一共有5133個質數 -
你在這里會發現一個潛在的需求,可能不光是你這么一個函式有統計時間的需求,其他函式一樣有,但現在這種處理方法可能要在每個目標函式上去加那段時間處理的代碼,非常麻煩,那有沒有好的做法呢?答案就是裝飾器,
裝飾器
-
改造(對比下跟之前的區別)
-
獲取質數個數函式,不需要統計時間
def get_prime_nums(): prime_nums = 0 for num in range(2,50000): if is_prime(num): prime_nums = prime_nums + 1 print(f'一共有{prime_nums}個質數') -
寫一個裝飾器的函式(不用管為何這么寫,以后會詳細說明)
def count_time(func): def wrapper(): from time import time start_time = time() func() end_time = time() print(f'統計花了{end_time-start_time}時間') return wrapper -
給要加時間的函式套上這個裝飾器
@count_time def get_prime_nums(): prime_nums = 0 ... # 不重復了 -
再次執行get_prime_nums()效果跟之前是一樣的
-
同樣的你可以將這個裝飾器運用到其他函式上去
@count_time def get_odd_nums(): odd_nums = 0 for num in range(2,50000): if num % 2 == 1: odd_nums = odd_nums + 1 print(f'一共有{odd_nums}個奇數') get_odd_nums() -
完了嗎,沒有,可能性還有很多,主要是被裝飾函式的變化導致了裝飾器本身要隨之適應變化,
裝飾器改造一
-
如果被裝飾的函式有回傳值呢?
@count_time def get_prime_nums(): prime_nums = 0 for num in range(2,50000): if is_prime(num): prime_nums = prime_nums + 1 return prime_nums -
此時你直接呼叫函式,而不改造裝飾器的話,是無法得到這個數量的
get_prime_nums() # 統計花了0.032898664474487305時間 print(get_prime_nums()) # 統計花了0.039182424545288086時間 # None -
改造裝飾器
-
如何改造呢?你應該要去理解裝飾器的運行原理(沒那么復雜,但我們這個課不深入,僅作為案例給你展示)
def count_time(func): def wrapper(): from time import time start_time = time() result = func() # 改動1: 用一個變數來接收func()的回傳 end_time = time() print(f'統計花了{end_time-start_time}時間') return result # 改動2: return出去 return wrapper -
此時你再執行
print(get_prime_nums()) # 統計花了0.054421424865722656時間 # 5133 就能看到這個回傳值了 -
完了嗎?還沒有,如果我們的被裝飾函式有引數呢?
裝飾器改造二
-
你的被裝飾函式存在引數
@count_time def get_prime_nums(end): prime_nums = 0 for num in range(2,end): if is_prime(num): prime_nums = prime_nums + 1 return prime_nums print(get_prime_nums(50000)) -
其實在IDE中get_prime_nums(50000)就會提示你意外實參
-
執行結果
Traceback (most recent call last): File "...\demo.py", line 37, in <module> print(get_prime_nums(50000)) TypeError: wrapper() takes 0 positional arguments but 1 was given -
這是初學者最困惑的地方了,等我們講了原理(或者說訣竅)你應該就非常清楚為何會這樣報錯了
-
怎么修改呢?
def count_time(func): def wrapper(*args): # 改動1: 增加一個不定引數 from time import time start_time = time() result = func(*args) # func也增加 end_time = time() print(f'統計花了{end_time-start_time}時間') return result return wrapper -
再次執行,就ok了
print(get_prime_nums(50000)) # 統計花了0.029825448989868164時間 # 5133 -
但是要注意,這樣的話,如果你的被裝飾函式是之前的沒有引數的情況,是會報錯的
# 回到過去 @count_time def get_prime_nums(): prime_nums = 0 for num in range(2,50000): if is_prime(num): prime_nums = prime_nums + 1 return prime_nums print(get_prime_nums(50000)) -
報錯
Traceback (most recent call last): File "...\demo.py", line 37, in <module> print(get_prime_nums(50000)) File "...\demo.py", line 22, in wrapper result = func(*args) TypeError: get_prime_nums() takes 0 positional arguments but 1 was given 行程已結束,退出代碼為 1 -
但由于是*args,你改成多個引數倒是可以的
@count_time 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)) # 可以執行
-
如果你這樣呼叫
print(get_prime_nums(start=2,end=50000)) -
報錯
Traceback (most recent call last): File "...\demo.py", line 37, in <module> print(get_prime_nums(start=2,end=50000)) TypeError: wrapper() got an unexpected keyword argument 'start' 行程已結束,退出代碼為 1 -
可以這樣修改你的裝飾器
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
說在最后
- 這個案例是入門的,講解了裝飾器的一些簡單使用
- 但,留了一些坑,你可能未必知道為何要這么修改,裝飾器是怎么調度的等等
- 且聽下回分解
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543385.html
標籤:Python
