前言:
寫代碼嘛,關鍵是得讓它既好用又好看,這不,Python裝飾器就擺在那兒。咱們程序員有時也得有那么點藝術家的腔調:講求效率,追求代碼的簡潔優雅,偶爾還得裝裝X,不是嗎?
翻開人家的工程代碼,哎呦,有時候我都忍不住想吐槽,怎么就這么啰嗦?這不,咱就得拿出刀子,削削刻刻,把裝飾器這玩意兒的妙用給咱整明白了。得讓代碼那叫一個流暢,看著清清楚楚,懂得一看就會,會了還想看。
正文:
1.?@staticmethod
作用
@staticmethod 裝飾器允許我們定義一個方法,使其不依賴于對象的狀態(即不與特定的實例self綁定,也不與類cls綁定)。這主要用在你想將某個功能置于類的命名空間(namespace)下,以表明它邏輯上屬于類,但實際上又獨立于類的任何特定實例。
用法
當你需要直接通過類調用方法而不需要實例時,將該方法定義為靜態方法是非常合適的。
class MathOperations:@staticmethoddef add(x, y):"""返回兩個參數的和。"""return x + y# 調用靜態方法,注意如何使用類名直接調用靜態方法,而不是類的實例
result = MathOperations.add(5, 10)
print(result) # 輸出: 15
代碼講解
在上述代碼中,add 方法被 @staticmethod 裝飾,意味著它不接受常規方法第一個參數(self)。這使得它可以像普通函數一樣通過類名直接調用,而不需要創建類的實例。
對比
不使用 @staticmethod 的普通方法會要求傳遞一個實例作為第一個參數。下面是未使用 @staticmethod 的對比:
class MathOperations:def add(self, x, y):"""返回兩個參數的和。需要通過實例調用。"""return x + y# 創建類的實例
math_ops = MathOperations()
result = math_ops.add(5, 10)
print(result) # 輸出: 15
在這個例子中,我們必須先創建一個 MathOperations 類的實例,并通過這個實例調用 add 方法。
2.@classmethod
作用
@classmethod 裝飾器使方法變成類方法,它接收類本身作為第一個參數,通常命名為 cls。這使得這個方法可以訪問類屬性或調用其他類方法,而不依賴于具體的實例。
用法
類方法通常用作替代構造函數。
class MyClass:_my_list = []@classmethoddef add_to_list(cls, item):"""將項目添加到類屬性列表中。"""cls._my_list.append(item)MyClass.add_to_list('item 1')
print(MyClass._my_list) # 輸出: ['item 1']
代碼講解
在上述代碼中,add_to_list 方法是一個類方法,可以直接通過類來調用,并且它修改了類屬性 _my_list。
對比
常規的實例方法需要創建實例才能調用,并且只能訪問實例屬性:
class MyClass:def __init__(self):self._my_list = []def add_to_list(self, item):"""將項目添加到實例屬性列表中。"""self._my_list.append(item)# 創建類的實例
my_instance = MyClass()
my_instance.add_to_list('item 1')
print(my_instance._my_list) # 輸出: ['item 1']# 嘗試使用類名直接調用將會失敗
# MyClass.add_to_list('item 1') # 這將拋出 TypeError
3.?@property
作用
@property 裝飾器用于將類中的方法當作一個屬性來訪問,通常用于計算或返回內部狀態的值,同時不讓調用者直接訪問內部狀態。
用法
它經常被用于創建只讀屬性。
class MyClass:def __init__(self, value):self._internal_state = value@propertydef internal_state(self):"""訪問內部狀態的一個只讀屬性"""return self._internal_state# 創建類實例
instance = MyClass(5)
print(instance.internal_state) # 輸出: 5
代碼講解
在上面的代碼中,internal_state 成為一個只讀屬性,我們不能直接對它賦值(除非也定義了 setter)。
對比
沒有 @property 裝飾器,我們通常會直接公開屬性或通過一個明確的 getter 方法來訪問:
class MyClass:def __init__(self, value):self.internal_state = value# 創建類實例
instance = MyClass(5)
print(instance.internal_state) # 輸出: 5
在這個例子中,internal_state 可以被外部直接訪問和修改,沒有了 @property 提供的保護層。
4.?@property.setter
作用
@property.setter 裝飾器是與之前的 @property 配套使用的,它允許我們定義一個賦值方法,可以通過這個屬性對相關數據進行設置。
用法
我們可以用它來定義一個屬性的設置邏輯,例如實施類型檢查或值驗證。
class MyClass:def __init__(self, value):self._internal_state = value@propertydef internal_state(self):return self._internal_state@internal_state.setterdef internal_state(self, value):if not isinstance(value, int):raise ValueError("internal_state must be an integer")self._internal_state = value# 創建類實例
instance = MyClass(5)
instance.internal_state = 10 # 有效
print(instance.internal_state) # 輸出: 10
代碼講解
在這個例子中,嘗試為 internal_state 賦非整數值會引發 ValueError,因為我們的 setter 方法中添加了類型檢查。
對比
如果沒有 @property.setter,我們不能定義一個屬性的賦值行為,屬性將是完全公開的:
class MyClass:def __init__(self, value):self.internal_state = value# 創建類實例
instance = MyClass(5)
instance.internal_state = 'a string' # 沒有問題,但可能不是我們想要的行為
在沒有 setter 裝飾器的情況下,internal_state 屬性可以接受任何類型的賦值,沒有額外的驗證邏輯來保護類的內部表示。
5.?@functools.wraps
作用
裝飾器在Python中用于增加函數額外的功能,但裝飾器會改變函數的__name__和__doc__屬性。@functools.wraps是一個特殊的裝飾器,用于在定義裝飾器時保持原函數的元數據不變。
用法
這通常用于自定義裝飾器的編寫中,以確保被裝飾函數的元數據不會丟失。
import functoolsdef my_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):print("函數發生前...")result = func(*args, **kwargs)print("函數發生后")return resultreturn wrapper@my_decorator
def say_hello():"""Greet the user."""print("Hello!")# 由于使用了 functools.wraps,say_hello 保留了其原始的元數據
print(say_hello.__name__)
print(say_hello.__doc__)
代碼講解
在這個例子中,functools.wraps用在內部函數wrapper上,保護了原始函數say_hello的__name__和__doc__屬性。如果不使用functools.wraps,say_hello.__name__將會返回'wrapper'。
對比
沒有@functools.wraps,函數say_hello的屬性會被丟失:
def my_decorator(func):def wrapper(*args, **kwargs):"""Wrapper function for func."""print("函數get前發生的...")result = func(*args, **kwargs)print("函數get后發生的...")return resultreturn wrapper@my_decorator
def say_hello():print("Hello!")# 沒有使用 functools.wraps,元數據被覆蓋了
print(say_hello.__name__)
print(say_hello.__doc__)
在沒有使用functools.wraps的這個版本中,裝飾函數wrapper的__name__和__doc__覆蓋了say_hello的元數據,這會在某些情況下導致混淆,特別是在需要函數簽名保持原樣時。
6.?@functools.lru_cache
作用
@functools.lru_cache 提供了一個簡單的緩存機制,讓函數能緩存最近使用過的輸入及其結果。如果一個函數被頻繁調用,并且伴隨相同的參數,LRU(Least Recently Used)緩存可以提升性能。
用法
它特別用于那些計算開銷大但又有高重復調用概率的函數。
import functools@functools.lru_cache(maxsize=32)
def fibonacci(num):"""返回斐波那契數列的第 num 個數"""if num < 2:return numreturn fibonacci(num - 1) + fibonacci(num - 2)# 第一次調用將會緩存結果
print(fibonacci(10)) # 輸出: 55
# 后續調用將會使用緩存
print(fibonacci(10)) # 輸出: 55
代碼講解
在這個例子中,fibonacci 函數的每個結果會在首次計算后緩存起來。當你再次用同樣的參數調用該函數時,它會返回緩存的結果而不是重新計算。
對比
不使用 @functools.lru_cache 運行上面的 fibonacci 函數會導致很多重復的計算,特別是參數較大時會非常耗時。
7.?@functools.singledispatch
作用
@functools.singledispatch 裝飾器可以將普通函數轉化為單分派泛函數。這表示函數將會根據第一個參數的類型,調用另外專門定義的函數,實現類似于其他語言中的函數重載。
用法
它特別適用于需要根據不同類型執行不同操作的場景。
import functools@functools.singledispatch
def format_data(arg):"""默認的實現,被調用時參數類型沒有匹配的特定注冊函數"""return str(arg)@format_data.register
def _(arg: int):"""專門用于處理整數的實現"""return f"{arg} is an integer"@format_data.register
def _(arg: list):"""專門用于處理列表的實現"""return f"List length is {len(arg)} and items are {', '.join(map(str, arg))}"# 根據參數類型調用不同的實現
print(format_data("Hello!"))
print(format_data(42))
print(format_data([1, 2, 3]))
代碼講解
在這個例子中,對于任意未注冊類型,format_data 將使用默認實現,即直接轉換為字符串。對于被注冊類型,例如 int 和 list,將調用專為它們定義的函數。
對比
若沒有 @functools.singledispatch,你通常需要編寫顯式的類型檢查語句(如使用 if-elif-else 結構),這樣的代碼通常會更加冗長和不易維護。
總結:
好了,我的粉兒們,咱們今天的`裝飾器課`就上到這兒。
記住,裝飾器不光讓你的代碼行數變短,顏值變高,更重要的是,它能讓你的工作流、邏輯變得更清晰。
- @staticmethod和@classmethod能幫你搞定靜態和類相關的調用;
- @property 讓訪問方法像是讀取屬性一樣優雅;
- @functools.wraps,讓你自定義裝飾器的時候還能保持原函數的尊嚴和名聲。
- 然后是咱們的性能加速器@functools.lru_cache,讓你對那些費勁兒的計算結果記個小本本,下次用的時候直接從記憶里找,不再重新煮沸冷飯,節約不少腦力。
- 至于@functools.singledispatch,簡直就是動態語言Python的類型掛鉤利器,讓同一個名字的函數根據傳參的不同表演不同的戲法。
啰嗦了這么多,希望你們明白:代碼寫得好不好,跟會不會用裝飾器,那可是直接掛鉤的。用好了,你的代碼就像是精品咖啡,不用或者用糟了,那就是旅館速溶。行了,回家練去吧。
保持好奇,保持饑渴,這樣才能寫出更香的代碼。加油!