2.23. 装饰器

装饰器是一种把一个函数(或方法)包装进另一个函数的语法。包装器可以在原始调用前后添加行为、替换返回值,或附加元数据。其形式为:

@wrapper
def f():
    ...

@wrapper 这一行等价于 f = wrapper(f) ——函数 f 正常构建,然后被交给 wrapper,其结果被重新绑定到名称 f

一个函数从左侧进入"wrapper"盒子, 并从右侧作为被装饰的函数出现。

装饰器接收一个函数并返回一个新函数。

2.23.1. 内置方法装饰器

Python 自带了几个装饰器,用在类体内部。

2.23.1.1. @property

把一个方法变成计算属性。调用者像访问普通属性那样访问它(不带括号),但每次读取时都会运行一个方法:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(5)
print(c.area)             # 78.53975  -- no parentheses

当一个看起来简单的属性背后需要做一点小计算时,请使用它。如果计算开销很大,则更应使用普通方法——调用者不会预期读取属性会很慢。

2.23.1.2. @classmethod

定义一个接收而非实例作为第一个参数的方法。第一个参数按惯例命名为 cls

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    @classmethod
    def origin(cls):
        return cls(0, 0)

p = Point.origin()

类方法是提供替代构造函数的标准方式——即以非默认方式返回构建好的实例的工厂函数。

2.23.1.3. @staticmethod

定义一个既不接收实例也不接收类的方法——它只是一个出于组织原因而存在于类命名空间中的普通函数:

class Temperature:
    @staticmethod
    def c_to_f(c):
        return c * 9 / 5 + 32

Temperature.c_to_f(100)   # 212.0

请谨慎使用它;如果一个函数确实与类的状态毫无关系,那么放在模块级别的普通函数通常更清晰。

2.23.2. 编写自定义装饰器

装饰器是一个接收函数并返回函数的函数。其最简形式:

def log_calls(func):
    def wrapper(*args, **kwargs):
        print("calling", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(2, 3)

输出::

calling add

wrapper 闭包捕获了 func 并将一切转发给它。*args / **kwargs 让它能作用于任何函数,而不只是双参数的函数。这种模式是更精巧装饰器(计时、缓存、失败重试)的基础,但其核心始终不变:接收一个函数,返回一个函数。