Python 裝飾器允許您在不修改函數(shù)本身的情況下更改函數(shù)的行為。在本文中,我將向您展示如何創(chuàng)建和使用裝飾器。您將看到使用這個(gè) Python 高級語法是多么容易。
何時(shí)在 Python 中使用裝飾器
當(dāng)您需要更改函數(shù)的行為而不修改函數(shù)本身時(shí),您將使用裝飾器。一些很好的例子是當(dāng)您想要添加日志記錄、測試性能、執(zhí)行緩存、驗(yàn)證權(quán)限等時(shí)。
當(dāng)您需要在多個(gè)函數(shù)上運(yùn)行相同的代碼時(shí),您也可以使用裝飾器。這可以避免您編寫重復(fù)的代碼。
以下是用于創(chuàng)建 Python 裝飾器的構(gòu)建塊
為了更好地理解裝飾器的工作原理,您應(yīng)該先了解一些概念。
- 函數(shù)是一個(gè)對象。因此,可以將函數(shù)分配給變量。可以從該變量訪問該函數(shù)。
def my_function():
print('I am a function.')
# Assign the function to a variable without parenthesis. We don't want to execute the function.
description = my_function
# Accessing the function from the variable I assigned it to.
print(description())
# Output
'I am a function.'
2. 一個(gè)函數(shù)可以嵌套在另一個(gè)函數(shù)中。
def outer_function():
def inner_function():
print('I came from the inner function.')
# Executing the inner function inside the outer function.
inner_function()
outer_function()
# Output
I came from the inner function.
請注意,inner_function在outer_function內(nèi)部. 如果我嘗試執(zhí)行inner_function外部,outer_function我會收到 NameError 異常。
inner_function()
Traceback (most recent call last):
File "/tmp/my_script.py", line 9, in <module>
inner_function()
NameError: name 'inner_function' is not defined
3. 由于一個(gè)函數(shù)可以嵌套在另一個(gè)函數(shù)中,所以它也可以被返回。
def outer_function():
'''Assign task to student'''
task = 'Read Python book chapter 3.'
def inner_function():
print(task)
return inner_function
homework = outer_function()
homework()
# Output
'Read Python book chapter 5.'
4. 一個(gè)函數(shù)可以作為參數(shù)傳遞給另一個(gè)函數(shù)。
def friendly_reminder(func):
'''Reminder for husband'''
func()
print('Don\'t forget to bring your wallet!')
def action():
print('I am going to the store buy you something nice.')
# Calling the friendly_reminder function with the action function used as an argument.
friendly_reminder(action)
# Output
I am going to the store buy you something nice.
Don't forget to bring your wallet!
如何創(chuàng)建 Python 裝飾器
為了在 Python 中創(chuàng)建裝飾器函數(shù),我創(chuàng)建了一個(gè)將函數(shù)作為參數(shù)的外部函數(shù)。還有一個(gè)內(nèi)部函數(shù)環(huán)繞裝飾函數(shù)。
以下是基本 Python 裝飾器的語法:
def my_decorator_func(func):
def wrapper_func():
# Do something before the function.
func()
# Do something after the function.
return wrapper_func
要使用裝飾器,您可以將其附加到一個(gè)函數(shù),就像您在下面的代碼中看到的那樣。我們通過將裝飾器的名稱直接放在我們想要使用它的函數(shù)上方來使用裝飾器。你用一個(gè)@符號作為裝飾器函數(shù)的前綴。
@my_decorator_func
def my_func():
pass
這是一個(gè)簡單的例子。此裝飾器記錄執(zhí)行函數(shù)的日期和時(shí)間:
from datetime import datetime
def log_datetime(func):
'''Log the date and time of a function'''
def wrapper():
print(f'Function: {func.__name__}\nRun on: {datetime.today().strftime("%Y-%m-%d %H:%M:%S")}')
print(f'{"-"*30}')
func()
return wrapper
@log_datetime
def daily_backup():
print('Daily backup job has finished.')
daily_backup()
# Output
Daily backup job has finished.
Function: daily_backup
Run on: 2021-06-06 06:54:14
---------------------------
如何在 Python 中為裝飾器添加參數(shù)
裝飾器可以將參數(shù)傳遞給它們。要向裝飾器添加參數(shù),我將*args 和 **kwargs添加到內(nèi)部函數(shù)。
- *args采取的任何類型的參數(shù),數(shù)據(jù)類型不受限制,比如10,True,或'Brandon'。
- **kwargs采取任何類型的關(guān)鍵字參數(shù),例如count=99, is_authenticated=True, 或name='Brandon'。
這是一個(gè)帶參數(shù)的裝飾器:
def my_decorator_func(func):
def wrapper_func(*args, **kwargs):
# Do something before the function.
func(*args, **kwargs)
# Do something after the function.
return wrapper_func
@my_decorator_func
def my_func(my_arg):
'''Example docstring for function'''
pass
裝飾者隱藏他們正在裝飾的函數(shù)。如果我檢查__name__or__doc__方法,我們會得到一個(gè)意想不到的結(jié)果。
print(my_func.__name__)
print(my_func.__doc__)
# Output
wrapper_func
None
為了解決這個(gè)問題,我將使用functools. Functools 包裝將使用裝飾函數(shù)屬性更新裝飾器。
from functools import wraps
def my_decorator_func(func):
@wraps(func)
def wrapper_func(*args, **kwargs):
func(*args, **kwargs)
return wrapper_func
@my_decorator_func
def my_func(my_args):
'''Example docstring for function'''
pass
現(xiàn)在我收到了我期待的輸出。
print(my_func.__name__)
print(my_func.__doc__)
# Output
my_func
Example docstring for function
運(yùn)行中的 Python 裝飾器示例
我創(chuàng)建了一個(gè)裝飾器來測量函數(shù)的內(nèi)存和速度。我們將使用裝飾器通過四種方法測試性能列表生成:范圍、列表理解、追加和連接。
from functools import wraps
import tracemalloc
from time import perf_counter
def measure_performance(func):
'''Measure performance of a function'''
@wraps(func)
def wrapper(*args, **kwargs):
tracemalloc.start()
start_time = perf_counter()
func(*args, **kwargs)
current, peak = tracemalloc.get_traced_memory()
finish_time = perf_counter()
print(f'Function: {func.__name__}')
print(f'Method: {func.__doc__}')
print(f'Memory usage:\t\t {current / 10**6:.6f} MB \n'
f'Peak memory usage:\t {peak / 10**6:.6f} MB ')
print(f'Time elapsed is seconds: {finish_time - start_time:.6f}')
print(f'{"-"*40}')
tracemalloc.stop()
return wrapper
@measure_performance
def make_list1():
'''Range'''
my_list = list(range(100000))
@measure_performance
def make_list2():
'''List comprehension'''
my_list = [l for l in range(100000)]
@measure_performance
def make_list3():
'''Append'''
my_list = []
for item in range(100000):
my_list.append(item)
@measure_performance
def make_list4():
'''Concatenation'''
my_list = []
for item in range(100000):
my_list = my_list + [item]
print(make_list1())
print(make_list2())
print(make_list3())
print(make_list4())
# Output
Function: make_list1
Method: Range
Memory usage: 0.000072 MB
Peak memory usage: 3.693040 MB
Time elapsed is seconds: 0.049359
----------------------------------------
Function: make_list2
Method: List comprehension
Memory usage: 0.000856 MB
Peak memory usage: 3.618244 MB
Time elapsed is seconds: 0.052338
----------------------------------------
Function: make_list3
Method: Append
Memory usage: 0.000448 MB
Peak memory usage: 3.617692 MB
Time elapsed is seconds: 0.060719
----------------------------------------
Function: make_list4
Method: Concatenation
Memory usage: 0.000440 MB
Peak memory usage: 4.393292 MB
Time elapsed is seconds: 61.649138
----------------------------------------
您也可以將裝飾器與類一起使用。讓我們看看如何在 Python 類中使用裝飾器。
在這個(gè)例子中,注意沒有@涉及任何字符。使用該__call__方法,在創(chuàng)建類的實(shí)例時(shí)執(zhí)行裝飾器。
此類跟蹤查詢 API 的函數(shù)已運(yùn)行的次數(shù)。一旦達(dá)到限制,裝飾器就會停止執(zhí)行該函數(shù)。
import requests
class LimitQuery:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.limit = args[0]
if self.count < self.limit:
self.count += 1
return self.func(*args, **kwargs)
else:
print(f'No queries left. All {self.count} queries used.')
return
@LimitQuery
def get_coin_price(limit):
'''View the Bitcoin Price Index (BPI)'''
url = requests.get('https://api.coindesk.com/v1/bpi/currentprice.json')
if url.status_code == 200:
text = url.json()
return f"${float(text['bpi']['USD']['rate_float']):.2f}"
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
print(get_coin_price(5))
# Output
$35968.25
$35896.55
$34368.14
$35962.27
$34058.26
No queries left. All 5 queries used.
這個(gè)類將跟蹤類的狀態(tài)。