循序渐进讲解 Python 装饰器

循序渐进讲解 Python 装饰器


1. 装饰器简介


装饰器被加到 Python 中是为了使对函数和方法的包装(wrapping)更容易阅读和理解。最初的使用场景是:为了能够在方法定义的头部通过装饰器让其定义成类方法或静态方法。如果没有装饰器语法,想要定义静态方法或类方法时,就必须使用一种很少见又啰嗦的定义方式:

class WithoutDecorators:
    def some_static_method():
        print("this is static method")
    some_static_method = staticmethod(some_static_method)

    def some_class_method(cls):
        print("this is class method")
    some_class_method = classmethod(some_class_method)


如果使用装饰器语法来达到同样目的的话,代码就会变得简单易懂:

class WithDecorators:
    @staticmethod
    def some_static_method():
        print("this is static method")

    @classmethod
    def some_class_method(cls):
        print("this is class method")

2. 装饰器的语法和可能实现

通常来说,装饰器是一个具名对象(即,不可以是 lambda 表达式是不允许的),调用时它接受单个参数(被装饰的那个函数),并且返回另一个可调用对象。这里说返回可调用对象,而不说返回函数是有原因的。尽管讨论装饰器时,常将其限定在方法和函数这个范围内,但其实装饰圈不只局限于这两个方面。事实上,任何可调用对象(实现了 __call__ 方法的对象都是可调用的 )都能被当作装饰器,并且这些装饰器返回的对象常常不是简单的函数,而是实现了自己的 __call__ 方法的更复杂类的实例。

装饰器语法只是个语法糖。来看看下面装饰器的用法:

@some_decorator
def decorated_function():
    pass
这种定义总是能够使用显式的装饰器调用和函数重新赋值来替代:

def decorated_function():
    pass
decorated_function = some_decorator(decorated_function)

然而后者可读性差,并且如果一个函数被多个装饰器修饰的话,就会变得难以理解。


Note:其实装饰器甚至不需要返回可调用对象。事实上任何函数都可以被用作装饰器,因为 Python 并未强制装饰器的返回值类型。因此使用一些接受单个参数,并且不返回可调用对象的函数(如,str)作为装饰器,在语法上是完全合法的。但是如果用户尝试调用经过这样装饰出来的对象的话,最终会失败。不管怎么说,这种装饰器语法,能让我们做一些有趣的实验。


3. 函数形式的装饰器定义

有许多方式可用来来写装饰器,但最简单的方式还是写一个返回子函数的函数,在子函数中包装对想要装饰的那个函数的调用。看起来就像下面这样:

def mydecorator(function):
    def wrapped(*args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result
    # return wrapper as a decorated function
    return wrapped

4. 类形式的装饰器定义

总的来说,装饰器总是可以使用函数来实现的,而有些情况下,使用用户自定义的类来实现装饰器是个更好的选择。当装饰器需要复杂的参数或装饰器依赖某种状态的话,就可以选择用类来实现。以类的方式,实现一个无参数的装饰器,看起来就像下面这样:

class DecoratorAsClass:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = self.function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result

5. 带参数的装饰器

在实际代码中,常需要用到能接受参数的装饰器。当使用函数作为装饰器时,解决方法比较简单:使用两层包装。下面是一个装饰器的简单例子,该装饰器会重复执行被装饰的函数指定次数:

def repeat(number=3):
"""Cause decorated function to be repeated a number of times.
Last value of original function call is returned as a result
:param number: number of repetitions, 3 if not specified
"""
    def actual_decorator(function):
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(number):
            result = function(*args, **kwargs)
            return result
        return wrapper
    return actual_decorator

以这种方式定义的装饰器可以接受参数:

>>> @repeat(2)
... def foo():
...     print("foo")
...
>>> foo()
foo
foo

必须注意的是,即使参数化的装饰器的参数有默认值,装饰器名后面的括号也是需要的。以默认值方式,使用上面的装饰器的正确方式如下:

>>> @repeat()
... def bar():
...     print("bar")
...
>>> bar()
bar
bar
bar

如果漏掉括号,那么在调用被装饰的函数时将会发生错误:

>>> @repeat
... def bar():
...     pass
...
>>> bar()
Traceback (most recent call last):
File "", line 1, in 
TypeError: actual_decorator() missing 1 required positional argument: 'function'

6. 自省保护装饰器(Introspection preserving decorators)

使用装饰器的一个常见陷进是没有保存被装饰函数的元数据(主要是文档字符串和函数原来的名称)。前面所有的例子都有这个问题。它们构造并返回了另一个与原先那个对象身份信息无关的新的对象。这使得对被装饰的函数进行调试变得困难,并且会破坏大多数自动化文档工具的使用,因为原先那个函数的文档字符串和函数签名已经无法访问了。

但是,让我们来细看一下这个问题。假如我们有个愚蠢的装饰器,这个装饰器除了装饰之外,什么也不做。我们还有一些被这个装饰器装饰了的函数:

def dummy_decorator(function):
    def wrapped(*args, **kwargs):
        """Internal wrapped function documentation."""
        return function(*args, **kwargs)
    return wrapped

@dummy_decorator
def function_with_important_docstring():
    """This is important docstring we do not want to lose."""

如果我们在 Python 交互式 Shell 里面检查这个被装饰的函数,我们会发现它已经丢掉了原先的名字和文档字符串:

>>> function_with_important_docstring.__name__
'wrapped'
>>> function_with_important_docstring.__doc__
'Internal wrapped function documentation.'

这个问题的一个解决方法是使用 functools 模块提供的内置装饰器 wraps():

from functools import wraps
def preserving_decorator(function):
    @wraps(function)
    def wrapped(*args, **kwargs):
        """Internal wrapped function documentation."""
        return function(*args, **kwargs)
    return wrapped

@preserving_decorator
def function_with_important_docstring():
    """This is important docstring we do not want to lose."""

使用这种方式定义装饰器,重要的函数元数据就会保存下来:


>>> function_with_important_docstring.__name__
'function_with_important_docstring.'
>>> function_with_important_docstring.__doc__
'This is important docstring we do not want to lose.'




你可能感兴趣的:(Python,装饰器)