Python3 装饰器
装饰器语法、日志与计时实战、带参数的装饰器、内置装饰器与堆叠顺序。
语法
@deco above a definition is sugar for func = deco(func). The decorator takes the original function and returns a new one, so calls run the wrapper with any logic you insert.
import functools
def deco(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
print("before", fn.__name__)
result = fn(*args, **kwargs)
print("after", fn.__name__)
return result
return wrapper
@deco
def hello(name):
"""打个招呼"""
return "hi " + name
print(hello("Tom"))
print(hello.__name__)
print(hello.__doc__)before hello
after hello
hi Tom
hello
打个招呼@functools.wraps(fn), hello.__name__ becomes wrapper and the docstring is lost — breaking logs, debugging and frameworks that inspect signatures.实例:打印日志
The classic use is logging arguments and timing in one place instead of repeating it in every function — pulling cross-cutting concerns out of business logic.
import functools
import time
def logged(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
cost = time.perf_counter() - start
print("%s(%s) -> %.4fs" % (fn.__name__, ", ".join(map(str, args)), cost))
return wrapper
@logged
def add(a, b):
return a + b
print(add(1, 2))
print(add(10, 20))add(1, 2) -> 0.0000s
3
add(10, 20) -> 0.0000s
30带参数的装饰器
Passing arguments needs one more layer: the outermost function takes the parameters and returns the real decorator. The chain is repeat(3) → decorator → wrapper, hence three nested functions.
import functools
def repeat(times):
def deco(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = fn(*args, **kwargs)
return result
return wrapper
return deco
@repeat(3)
def say(msg):
print(msg)
return msg
print(say("hi"))hi
hi
hi
hi内置装饰器
The three you meet most: @property exposes a method as an attribute, @staticmethod defines a helper with no self, @classmethod defines a factory operating on the class. functools.lru_cache memoises results.
import functools
class Temp:
def __init__(self, c):
self._c = c
@property
def f(self):
return self._c * 9 / 5 + 32
@staticmethod
def valid(c):
return -273.15 <= c
@classmethod
def from_f(cls, f):
return cls((f - 32) * 5 / 9)
t = Temp(100)
print(t.f, Temp.valid(-300), round(Temp.from_f(212)._c, 2))
@functools.lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(30))
print(fib.cache_info().hits > 0)212.0 False 100.0
832040
True多个装饰器的堆叠
Decorators wrap from the bottom up: the one nearest the function wraps first, and calls enter from the outermost. Expand it to a(b(func)) to see the order.
import functools
def tag(name):
def deco(fn):
@functools.wraps(fn)
def wrapper(*a, **k):
return "<%s>%s</%s>" % (name, fn(*a, **k), name)
return wrapper
return deco
@tag("b")
@tag("i")
def text():
return "x"
print(text())
print(text.__wrapped__.__name__)<b><i>x</i></b>
text语法
@deco 放在函数定义上方,等价于 func = deco(func)。装饰器接收原函数、返回新函数,调用时执行的是新函数 —— 可以在前后插入任意逻辑。
import functools
def deco(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
print("before", fn.__name__)
result = fn(*args, **kwargs)
print("after", fn.__name__)
return result
return wrapper
@deco
def hello(name):
"""打个招呼"""
return "hi " + name
print(hello("Tom"))
print(hello.__name__)
print(hello.__doc__)before hello
after hello
hi Tom
hello
打个招呼@functools.wraps(fn) 时,hello.__name__ 会变成 wrapper,文档字符串也丢失 —— 这会破坏日志、调试和依赖签名的框架。实例:打印日志
最典型的用途是统一记录入参与耗时,避免在每个函数里重复写。装饰器把这类横切关注点从业务逻辑中抽出来。
import functools
import time
def logged(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
cost = time.perf_counter() - start
print("%s(%s) -> %.4fs" % (fn.__name__, ", ".join(map(str, args)), cost))
return wrapper
@logged
def add(a, b):
return a + b
print(add(1, 2))
print(add(10, 20))add(1, 2) -> 0.0000s
3
add(10, 20) -> 0.0000s
30带参数的装饰器
要传参就得再包一层:最外层接收参数并返回真正的装饰器。调用链是 repeat(3) → 装饰器 → wrapper,所以写起来是三层函数。
import functools
def repeat(times):
def deco(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = fn(*args, **kwargs)
return result
return wrapper
return deco
@repeat(3)
def say(msg):
print(msg)
return msg
print(say("hi"))hi
hi
hi
hi内置装饰器
标准库里最常用的三个:@property 把方法变属性、@staticmethod 定义无 self 的工具函数、@classmethod 定义操作类本身的工厂方法。functools.lru_cache 则用来缓存结果。
import functools
class Temp:
def __init__(self, c):
self._c = c
@property
def f(self):
return self._c * 9 / 5 + 32
@staticmethod
def valid(c):
return -273.15 <= c
@classmethod
def from_f(cls, f):
return cls((f - 32) * 5 / 9)
t = Temp(100)
print(t.f, Temp.valid(-300), round(Temp.from_f(212)._c, 2))
@functools.lru_cache(maxsize=None)
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(30))
print(fib.cache_info().hits > 0)212.0 False 100.0
832040
True多个装饰器的堆叠
多个装饰器从下往上包裹:最靠近函数的最先执行包装,调用时从最外层进入。理解顺序的关键是把它展开成 a(b(func))。
import functools
def tag(name):
def deco(fn):
@functools.wraps(fn)
def wrapper(*a, **k):
return "<%s>%s</%s>" % (name, fn(*a, **k), name)
return wrapper
return deco
@tag("b")
@tag("i")
def text():
return "x"
print(text())
print(text.__wrapped__.__name__)<b><i>x</i></b>
text