概述

代码复用,语法糖@;

# funcA对funcB进行装饰
@funcA
def funcB(...):
    ...

# 使用的funcB已经被装饰/修改
funcB(...)

# 等价于
funcB = funcA(FuncB)
funcB(...)

闭包

要理解Python中的装饰器,先理解闭包(closure)概念。

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

#print_msg是外围函数
def print_msg():
    msg = "I'm closure"

    # printer是嵌套函数
    def printer():
        count += 1
        print(msg)

    return printer


# 这里获得的就是一个闭包
closure = print_msg()# 输出 I'm closure
closure()

msg是一个局部变量,在print_msg函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。 闭包就是引用了自有变量的函数,这个函数保存了执行的上下文,可以脱离原本的作用域独立存在。

简单装饰器

如果你想使用额外的代码包装一个函数,可以定义一个装饰器函数,例如:


import time


def timethis(func):
    '''    Decorator that reports the execution time.    '''
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

进阶版本:


import time
from functools import wraps


def timethis(func):
    '''    Decorator that reports the execution time.    '''

    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

值得注意的是@functools.wraps(func),这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。

使用装饰器:


>>>     @timethis
...     def countdown(n):
...     '''... Counts down...     '''
...         while n > 0:
...             n -= 1
...
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown(10000000)
countdown 0.87188299392912

相当于:


>>>     def countdown(n):
...     '''... Counts down...     '''
...          while n > 0:
...             n -= 1
...
>>> countdown = timethis(countdown)
>>> countdown(100000)
countdown 0.008917808532714844
>>> countdown(10000000)
countdown 0.87188299392912

带参数装饰器


from functools import wrapsimport logging

def logged(level, name=None, message=None):
    """    Add logging to a function. level is the logging    level, name is the logger name, and message is the    log message. If name and message aren't specified,    they default to the function's module and name.    """
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    return decorate

# Example use@logged(logging.DEBUG)def add(x, y):
    return x + y

@logged(logging.CRITICAL, 'example')def spam():
    print('Spam!')

初看起来,这种实现看上去很复杂,但是核心思想很简单。 最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数上面。 内层的函数 decorate() 接受一个函数作为参数,然后在函数上面放置一个包装器。 这里的关键点是包装器是可以使用传递给 logged() 的参数的

调用方式:


@decorator(x, y, z)
def func(a, b):
    pass

等价于:


def func(a, b):
    pass

func = decorator(x, y, z)(func)

decorator(x, y, z) 的返回结果必须是一个可调用对象,它接受一个函数作为参数并包装它, 可以参考9.7小节中另外一个可接受参数的包装器例子。


<
Previous Post
标准库: time
>
Next Post
迭代器和生成器