Python中的装饰器(Decorator)

@spiritree  September 26, 2016

装饰器实际上是一个函数,它可以让其他函数在不变动任何代码的情况下增加额外的功能,装饰器函数的返回值也是函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

了解装饰器的前提:Python中所有的函数都是对象

  • 可以被赋值为变量

  • 可以在另一个函数里被定义

所以函数可以return函数

简单装饰器

import logging
def use_logging(func):

    def wrapper(*args, **kwargs):
        logging.warn("%s is running" % func.__name__)
        return func(*args)
    return wrapper

@use_logging
#语法糖 等价于f = use_logging(f)
def f():
    print("i am f")

f()

结果:WARNING:f is running
i am f

带参数的装饰器
装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@use_logging,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。

import logging
def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def f(name='f'):
    print("i am %s" % name)

f()

结果:i am f
WARNING:f is running
上面的use_logging是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。

functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表:例子

def log(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + "was called")
        return func(*args, **kwargs)
    return with_logging
@log
def f(x):
    "do math"
    return x * x
print(f.__name__)    # print ‘with_logging’
print(f.__doc__)     # print None

这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

from functools import wraps
def log(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging
@log
def f(x):
    "do math"
    return x * x
print(f.__name__)  # print 'f'
print(f.__doc__)   # print 'do math'

添加新评论