好用的Python装饰器

3 投票
2 回答
1281 浏览
提问于 2025-04-15 14:14

我该如何优雅地写一个装饰器呢?

具体来说,主要问题包括:与其他装饰器的兼容性、保持函数签名等。

我希望尽量避免依赖装饰器模块,但如果有足够的好处,我会考虑使用它。

相关内容

  • 保持被装饰函数的签名 - 这是一个更具体的问题。这里的答案是使用第三方装饰器模块,并用 @decorator.decorator 来标注装饰器。

2 个回答

6

使用functools可以保留函数的名称和文档说明。不过,函数的签名(也就是参数和返回值的格式)不会被保留。

直接来自于文档

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print 'Calling decorated function'
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print 'Called example function'
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'
5

写一个好的装饰器和写一个好的函数其实是一样的。这意味着,理想情况下,你应该使用文档字符串来说明这个装饰器的功能,并确保它能在你的测试框架中正常工作。

你一定要使用 decorator 库,或者更好的是,使用标准库中的 functools.wraps() 装饰器(从2.5版本开始就有了)。

除此之外,最好让你的装饰器专注于特定的功能,并设计得很好。如果你的装饰器需要特定的参数,就不要使用 *args**kw。而且一定要明确你期望的参数是什么,所以不要这样写:

def keep_none(func):
    def _exec(*args, **kw):
        return None if args[0] is None else func(*args, **kw)

    return _exec

... 应该写成 ...

def keep_none(func):
    """Wraps a function which expects a value as the first argument, and
    ensures the function won't get called with *None*.  If it is, this 
    will return *None*.

    >>> def f(x):
    ...     return x + 5
    >>> f(1)
    6
    >>> f(None) is None
    Traceback (most recent call last):
        ...
    TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
    >>> f = keep_none(f)
    >>> f(1)
    6
    >>> f(None) is None
    True"""

    @wraps(func)
    def _exec(value, *args, **kw):
        return None if value is None else func(value, *args, **kw)

    return _exec

撰写回答