Python3 装饰器

装饰器语法、日志与计时实战、带参数的装饰器、内置装饰器与堆叠顺序。

Advanced
Key Points装饰器是「接收函数、返回函数」的高阶函数;务必用 functools.wraps 保留元信息;多个装饰器从下往上包裹。

语法

@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.

Python
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__)
Output
before hello
after hello
hi Tom
hello
打个招呼
Without @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.

Python
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))
Output
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.

Python
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"))
Output
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.

Python
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)
Output
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.

Python
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__)
Output
<b><i>x</i></b>
text

Friends & Partners