Python/裝飾器
Python的裝飾器是高階函數的語法糖。
一個函數decorators用於函數定義,它位於在函數定義之前的一行。例如:
@myDecorator def aFunction(): print "inside aFunction"
當編譯器處理這段代碼時,aFunction()被編譯然後將結果函數對象傳遞給myDecorator代碼,後者創建一個類函數對象並取代原來的aFunction()。
Decorator所返回的對象唯一的約束是它可以作為函數使用。意味著它必須是可調用的。因此,作為decorators使用的任何一個類必須實現__call__。
簡介
[編輯]屬性裝飾器(property decorator)的最簡單例子:
>>> class Foo(object):
... @property
... def bar(self):
... return 'baz'
...
>>> F = Foo()
>>> print F.bar
baz
上例是下述代碼的語法糖:
>>> class Foo(object):
... def bar(self):
... return 'baz'
... bar = property(bar)
...
>>> F = Foo()
>>> print F.bar
baz
泛型裝飾器(generic decorator)的最簡單例子:
>>> def decorator(f):
... def called(*args, **kargs):
... print 'A function is called somewhere'
... return f(*args, **kargs)
... return called
...
>>> class Foo(object):
... @decorator
... def bar(self):
... return 'baz'
...
>>> F = Foo()
>>> print F.bar()
A function is called somewhere
baz
下述例子列印出函數的每次調用及其實參:
#define the Trace class that will be
#invoked using decorators
class Trace(object):
def __init__(self, f): #接收被装饰函数
self.f =f
def __call__(self, *args, **kwargs): #实现装饰逻辑
print "entering function " + self.f.__name__
i=0
for arg in args:
print "arg {0}: {1}".format(i, arg)
i =i+1
return self.f(*args, **kwargs)
@Trace
def sum(a, b):
print "inside sum"
return a + b
>>> sum(3,2)
entering function sum
arg 0: 3
arg 1: 2
inside sum
或者用一個函數代替類作為裝飾器:
def Trace(f):
def my_f(*args, **kwargs):
print "entering " + f.__name__
result= f(*args, **kwargs)
print "exiting " + f.__name__
return result
my_f.__name = f.__name__
my_f.__doc__ = f.__doc__
return my_f
#An example of the trace decorator
@Trace
def sum(a, b):
print "inside sum"
return a + b
#if you run this you should see
>>> sum(3,2)
entering sum
inside sum
exiting sum
5
裝飾器是一個著名的設計模式,經常被用於有切面(aspect)需求的場景,如插入日誌、性能測試、事務處理等。裝飾器可以抽離出大量函數中與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的對象添加額外的功能。這種編程方式被稱為面向切面的編程(Aspect-Oriented Programming)。
Python於是提供了一些語法糖來降低為使用裝飾器而帶來的原始碼輸入量。
import time
def timeit(func):
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
return wrapper
@timeit
def foo():
print 'in foo()'
foo()
內置的裝飾器有三個,分別是staticmethod、classmethod和property,分別把類中定義的實例方法變成靜態方法、類方法和類屬性。由於模塊里可以定義函數,所以靜態方法和類方法的用處並不是太多,除非你想要完全的物件導向編程。屬性也不是不可或缺的。
class Rabbit(object):
def __init__(self, name):
self._name = name
@staticmethod
def newRabbit(name):
return Rabbit(name)
@classmethod
def newRabbit2(cls):
return Rabbit('')
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
functools模塊
[編輯]def wrapper(func):
def inner_function():
pass
return inner_function
def wrapped():
pass
print(wrapper(wrapped).__name__)
#inner_function
如何避免這種情況的產生?方法是使用 functools .wraps 裝飾器,它的作用就是將 被修飾的函數(wrapped) 的一些屬性值賦值給 修飾器函數(wrapper) ,最終讓屬性的顯示更符合我們的直覺。
wraps(wrapped[, assigned][, updated]): 將裝飾過的函數的特殊屬性保留。
from functools import wraps
def wrapper(func):
@wraps(func)
def inner_function():
pass
return inner_function
@wrapper
def wrapped():
pass
print(wrapped.__name__)
# wrapped
wraps 其實是一個偏函數對象(partial),源碼如下
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
可以看到wraps其實就是調用了一個函數update_wrapper,知道原理後,我們改寫上面的代碼,在不使用 wraps的情況下,也可以讓 wrapped.__name__ 列印出 wrapped,代碼如下:
from functools import update_wrapper
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
def wrapper(func):
def inner_function():
pass
update_wrapper(inner_function, func, assigned=WRAPPER_ASSIGNMENTS)
return inner_function
@wrapper
def wrapped():
pass
print(wrapped.__name__)
帶參數的裝飾器
[編輯]不傳參的裝飾器,只能對被裝飾函數,執行固定邏輯。如果要用到兩個內容大體一致,只是某些地方不同的邏輯。不傳參的話,就要寫兩個裝飾器。裝飾器如何實現傳參呢,會比較複雜,需要多一層嵌套,最外層的裝飾器的函數接受這些參數。例如: 函數閉包實現的示例:
def decoratorFunctionWithArguments(arg1, arg2, arg3):
def wrap(f):
print("Inside wrap()")
def wrapped_f(*args):
print("Inside wrapped_f()")
print("Decorator arguments:", arg1, arg2, arg3)
f(*args)
print("After f(*args)")
return wrapped_f
return wrap
@decoratorFunctionWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
print('sayHello arguments:', a1, a2, a3, a4)
帶參數的情況與無參情況截然不同:__call__()對象不能再作為decorated函數使用了。帶參數情況的decoration方法調用構造函數,然後就馬上調用__call__(),後者只能包含一個參數(函數對象)且返回替代原有函數的decorated函數對象。注意decoration期間__call__()僅被調用一次,此後從__call__()返回的decorated函數就可以在實際調用中使用了。
帶參數的類裝飾器示例:
class decoratorWithArguments(object):
def __init__(self, arg1, arg2, arg3):#不再接收被装饰函数,而是接收装饰器传入参数。
"""
If there are decorator arguments, the function
to be decorated is not passed to the constructor!
"""
print "Inside __init__()"
self.arg1 = arg1
self.arg2 = arg2
self.arg3 = arg3
def __call__(self, f):#接收被装饰函数,实现装饰逻辑。
"""
If there are decorator arguments, __call__() is only called
once, as part of the decoration process! You can only give
it a single argument, which is the function object.
"""
print "Inside __call__()"
def wrapped_f(*args):
print "Inside wrapped_f()"
print "Decorator arguments:", self.arg1, self.arg2, self.arg3
f(*args)
print "After f(*args)"
return wrapped_f
@decoratorWithArguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
print 'sayHello arguments:', a1, a2, a3, a4
內置裝飾器與functools裝飾器
[編輯]- @classmethod 定義一個類方法,第一個參數是類本身(cls),常用於工廠函數或操作類屬性。
- @staticmethod 定義一個不依賴實例或類的工具方法,無需 self 或 cls 參數。
- @property 把方法偽裝成屬性訪問,提供了getter、setter、deleter功能
- @dataclass Python 3.7引入的裝飾器,用於自動生成數據容器類的特殊方法。它大大簡化了創建主要用於存儲數據的類的過程,自動生成 __init__(), __repr__(), __eq__() 等方法,讓開發者專注於資料結構本身而不是樣板代碼。
- @lru_cache 基於最近最少使用策略緩存函數結果,適合有限參數空間的純函數。
- @cache Python 3.9+ 簡化寫法,功能同 @lru_cache(maxsize=None)。
- @cached_property 結合 @property 和緩存機制,僅在首次訪問時計算並緩存屬性值。
- @wraps 寫裝飾器時用於保留原函數的元信息(如 __name__、__doc__)。
- @overload 用於靜態類型提示,給函數提供多個簽名,需配合類型檢查器使用。
- @singledispatch 基於參數類型自動分發函數邏輯,寫出清晰的「類型分支」函數。
- @contextmanager 簡潔地實現支持 with 語句的上下文管理器。
- @final 標記類或方法不可被繼承或重寫,提升代碼可控性。
@dataclass的參數:
from dataclasses import dataclass, field
@dataclass(
init=True, # 生成__init__方法
repr=True, # 生成__repr__方法
eq=True, # 生成__eq__方法
order=False, # 生成__lt__, __le__, __gt__, __ge__方法
unsafe_hash=False, # 生成__hash__方法(需谨慎使用)
frozen=False, # 创建不可变实例
match_args=True, # 支持结构化模式匹配(Python 3.10+)
kw_only=False, # 所有字段都必须作为关键字参数
slots=False # 使用__slots__优化内存(Python 3.10+)
)
class ConfigurableClass:
name: str
value: int