關(guān)于Python裝飾器
裝飾器使您能夠在不更改函數(shù)源代碼的情況下修改函數(shù)的行為,從而提供一種簡潔靈活的方式來增強和擴展函數(shù)的功能。
在本文中,我將詳細(xì)介紹如何在 Python 中使用裝飾器,并展示裝飾器在何處有用的示例。
快速功能回顧
簡而言之,函數(shù)是一種使用不同參數(shù)重復(fù)運行代碼塊的方法。
換句話說,它可以接受輸入,使用這些輸入來運行一些預(yù)定義的代碼集,然后返回一個輸出。
函數(shù)接受輸入,使用它來運行一組代碼并返回輸出
在 Python 中,一個函數(shù)是這樣寫的:
?def add_one(num):
? return num + 1
當(dāng)我們想調(diào)用它時,我們可以用括號寫出函數(shù)的名稱并傳入必要的輸入(參數(shù)):
?final_value = add_one(1)
?print(final_value) # 2
請注意,在大多數(shù)情況下,參數(shù)和形參的含義相同。它們是函數(shù)中使用的變量。
區(qū)別在于我們指的是哪里。參數(shù)是我們在調(diào)用函數(shù)時傳遞給函數(shù)的內(nèi)容,參數(shù)是函數(shù)中聲明的內(nèi)容。
如何將函數(shù)作為參數(shù)傳遞
通常,在調(diào)用帶有參數(shù)的函數(shù)時,我們會傳遞整數(shù)、浮點數(shù)、字符串、列表、字典和其他數(shù)據(jù)類型的值。
但是,我們還可以做的是將一個函數(shù)也作為參數(shù)傳遞:
?def inner_function():
? print("inner_function is called")
? ? ?
?def outer_function(func):
? print("outer_function is called")
? ?func()
? ?
?outer_function(inner_function)
?# outer_function is called
?# inner_function is called
? ? ?
在這個例子中,我們創(chuàng)建了兩個函數(shù):inner_function和outer_function。
outer_function有一個參數(shù)調(diào)用,func它在調(diào)用它自己之后調(diào)用它。
outer_function 首先執(zhí)行。然后它調(diào)用作為參數(shù)傳遞的函數(shù)
把它想象成我們?nèi)绾蜗駥Υ魏纹渌祷蜃兞恳粯訉Υ瘮?shù)。
正確的說法是函數(shù)是一等公民。這意味著它們就像任何其他對象一樣,可以作為參數(shù)傳遞給其他函數(shù)、分配給變量或由其他函數(shù)返回。
所以,outer_function可以接受一個函數(shù)作為參數(shù),并在執(zhí)行時調(diào)用它。
如何返回函數(shù)
能夠?qū)⒑瘮?shù)視為對象的另一個好處是我們可以在其他函數(shù)中定義它們并返回它們:
?def outer_function():
? print("outer_function is called")
? ? ?
? def inner_function():
? ? ? ?print("inner_function is called")
? ? ? ?
? return inner_function
請注意,在這個例子中,當(dāng)我們 return 時inner_function,我們沒有調(diào)用它。
我們只返回了對它的引用,以便我們稍后可以存儲和調(diào)用它:
?returned_function = outer_function()
?# outer_funciton is called
?
?returned_function()
?# inner_function is called
如果你和我一樣,這可能看起來很有趣,但你可能仍然想知道這在實際程序中有何用處??。這是我們稍后要看的內(nèi)容!
如何在 Python 中創(chuàng)建裝飾器
接受函數(shù)作為參數(shù),在其他函數(shù)中定義函數(shù),并返回它們正是我們在 Python 中創(chuàng)建裝飾器所需要知道的。我們使用裝飾器為現(xiàn)有功能添加額外的功能。
例如,如果我們想創(chuàng)建一個裝飾器,它會將任何函數(shù)的返回值加 1,我們可以這樣做:
?def add_one_decorator(func):
? def add_one():
? ? ? value = func()
? ? ? ? ?return value + 1
? ? ? ? ?
? return add_one
現(xiàn)在,如果我們有一個返回數(shù)字的函數(shù),我們可以使用這個裝飾器將 1 添加到它輸出的任何值。
?def example_function():
? return 1
? ? ?
?final_value = add_one_decorator(example_function)
?print(final_value()) # 2
在此示例中,我們調(diào)用該add_one_decorator函數(shù)并將引用傳遞給example_function.
當(dāng)我們調(diào)用該add_one_decorator函數(shù)時,它會創(chuàng)建一個新函數(shù),add_one并在其中定義并返回對該新函數(shù)的引用。我們將此函數(shù)存儲在變量中final_value。
所以,當(dāng)執(zhí)行final_value函數(shù)時,add_one函數(shù)被調(diào)用。
add_one然后,其中定義的函數(shù)將add_one_decorator調(diào)用example_function,存儲它的值,并將其加一。
最終,這會2返回并打印到控制臺。
代碼將如何執(zhí)行的過程
請注意我們?nèi)绾尾槐馗脑嘉募xample_function來修改其返回值并向其添加功能。這就是使裝飾器如此有用的原因!
澄清一下,裝飾器并不是 Python 特有的。它們是可以應(yīng)用于其他編程語言的概念。但是在 Python 中,您可以使用語法輕松地使用它們@。
如何@在 Python 中使用語法
人物
正如我們在上面看到的,當(dāng)我們要使用裝飾器時,我們必須調(diào)用裝飾器函數(shù)并傳入我們要修改的函數(shù)。
在 Python 中,我們可以利用@語法來提高效率。
?@add_one_decorator
?def example_function():
? return 1
通過@add_one_decorator在我們的函數(shù)上面寫,它等效于以下內(nèi)容:
?example_function = add_one_decorator(example_function)
這意味著無論何時我們調(diào)用example_function,我們實際上都是add_one在調(diào)用裝飾器中定義的函數(shù)。
如何與裝飾器傳遞參數(shù)
使用裝飾器時,我們可能還希望裝飾函數(shù)在從包裝函數(shù)調(diào)用時能夠接收參數(shù)。
例如,如果我們有一個函數(shù)需要兩個參數(shù)并返回它們的總和:
?def add(a,b):
? return a + b
? ? ?
?print(add(1,2)) # 3
如果我們使用裝飾器將 1 添加到輸出:
?def add_one_decorator(func):
? def add_one():
? ? ? value = func()
? ? ? ? ?return value + 1
? ? ? ? ?
? ? ?return add_one
? ? ?
?@add_one_decorator
?def add(a,b):
? return a + b
? ? ?
?add(1,2)
?# TypeError: add_one_decorator.<locals>.add_one() takes 0 positional arguments but 2 were given
這樣做時,我們遇到了一個錯誤:包裝函數(shù) ( add_one) 不接受任何參數(shù),但我們提供了兩個參數(shù)。
為了解決這個問題,我們需要在調(diào)用它時將接收到的任何參數(shù)傳遞add_one給裝飾函數(shù):
?def add_one_decorator(func):
? def add_one(*args, **kwargs):
? ? ? value = func(*args, **kwargs)
? ? ? ? ?return value + 1
? ? ? ? ?
? ? ? return add_one
? ? ?
? add_one_decorator
? def add(a,b):
? ?return a+b
? ? ?
? print(add(1,2)) # 4
我們使用*args和**kwargs來表示add_one包裝函數(shù)應(yīng)該能夠接收任意數(shù)量的位置參數(shù) ( args) 和關(guān)鍵字參數(shù) ( kwargs)。
?args`將是給定的所有位置關(guān)鍵字的列表,在這種情況下`[1,2].
kwargs將是一個字典,鍵作為使用的關(guān)鍵字參數(shù),值作為分配給它們的值,在本例中是一個空字典。
寫作func(*args, **kwargs)表明我們想要調(diào)用func與接收到的相同的位置和關(guān)鍵字參數(shù)
這確保傳遞給修飾函數(shù)的所有位置參數(shù)和關(guān)鍵字參數(shù)都將傳遞給原始函數(shù)。
為什么 Python 中的裝飾器有用?真實代碼示例
現(xiàn)在我們已經(jīng)了解了 Python 裝飾器到底是什么,讓我們看看裝飾器何時有用的一些真實示例。
記錄
在構(gòu)建較大的應(yīng)用程序時,記錄哪些函數(shù)執(zhí)行時使用了哪些信息的日志通常很有幫助,例如使用了哪些參數(shù),以及在應(yīng)用程序運行時函數(shù)返回了什么。
當(dāng)出現(xiàn)問題時,這對于故障排除和調(diào)試非常有用,有助于查明問題的根源。即使不用于調(diào)試,日志記錄也可用于監(jiān)視程序的狀態(tài)。
這是一個簡單的示例,說明我們?nèi)绾蝿?chuàng)建一個簡單的記錄器(使用內(nèi)置的 Pythonlogging包)以將有關(guān)我們的應(yīng)用程序運行的信息保存到名為的文件中main.log:
?import logging
?
?def function_logger(func):
? ? ?logging.basicConfig(level = logging.INFO, filename="main.log")
? ? ?def wrapper(*args, **kwargs):
? ? ? ? ?result = func(*args, **kwargs)
? ? ? ? ?logging.info(f"{func.__name__} ran with positional arguments: {args} and keyword arguments: {kwargs}. Return value: {result}")
? ? ? ? ?return result
? ? ?
? ? ?return wrapper
?
?@function_logger
?def add_one(value):
? ? ?return value + 1
?
?print(add_one(1))
每當(dāng)該add_one函數(shù)運行時,一個新日志將附加到main.log文件中:
?INFO:root:add_one ran with positional arguments: (1,) and keyword arguments: {}. Return value: 2
緩存
如果我們有一個應(yīng)用程序需要使用相同的參數(shù)多次運行相同的函數(shù),返回相同的值,它很快就會變得低效并占用不必要的資源。
為防止這種情況,在每次調(diào)用函數(shù)時存儲所使用的參數(shù)和函數(shù)的返回值可能很有用,如果我們已經(jīng)使用相同的參數(shù)調(diào)用了函數(shù),則只需重新使用返回值即可。
在 Python 中,這可以通過使用隨 Python 安裝的模塊@lru_cache中的裝飾器來實現(xiàn)。functools
LRU指的是Least Recently Used,意思是每當(dāng)調(diào)用函數(shù)時,都會存儲使用的參數(shù)和返回值。但是一旦此類條目的數(shù)量達(dá)到最大大?。J(rèn)情況下為 128),最近最少使用的條目將被刪除。
?from functools import lru_cache
?
?@lru_cache
?def fibonacci(n):
? ? ?if n <= 1:
? ? ? ? ?return n
? ? ?return fibonacci(n - 1) + fibonacci(n - 2)
在此示例中,函數(shù)fibonacci接受參數(shù)n,如果它小于1,則返回n,否則返回調(diào)用函數(shù)的總和n-1和n-2。
因此,如果使用 調(diào)用該函數(shù)n=10,它會返回55:
?print(fibnoacci(10))
?# 55
在這種情況下,當(dāng)我們調(diào)用函數(shù) 時fibonacci(10),它會調(diào)用函數(shù)fibonacci(9)and fibonacci(8),依此類推,直到到達(dá) 1 或 0。
如果我們要多次使用這個函數(shù):
?fibonacci(50)
?fibonacci(100)
我們可以利用已保存條目的緩存。因此,當(dāng)我們調(diào)用時,它可以在到達(dá)時fibonacci(50)停止調(diào)用該函數(shù),而當(dāng)我們調(diào)用時,它可以在到達(dá)時停止調(diào)用該函數(shù),從而使程序更加高效。fibonacci10fibonacci(100)``50
這些示例有一個共同點,那就是它們非常容易在 Python 中實現(xiàn)您預(yù)先存在的函數(shù)。您不需要更改代碼或手動將您的函數(shù)包裝在另一個代碼中。
能夠簡單地使用@語法使得利用其他模塊和包變得輕而易舉。
概括
Python 裝飾器可以毫不費力地擴展函數(shù)而無需修改它們。在本教程中,您了解了裝飾器的工作原理,并看到了一些可以使用它們的示例。