Python——装饰器decorator

Python中的装饰器可以在不修改原始函数代码的基础上,在Python函数中插入一些额外操作


简单装饰器

  • 装饰器定义

    1
    2
    3
    4
    5
    6
    def decorator(func):
    def wrapper(*args, **kwargs):
    print "decorator" # 每次函数 func 被调用时都会输出
    return func(*args, **kwargs)
    print "only once decorator" # 仅在函数定义时执行一次
    return wrapper
  • 装饰器使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @decorator
    def add(a, b):
    print "sum: ", a+b
    # 函数定义时输出:only once decorator

    # just like a normal function
    add(10, 20)

    # output:
    decorator
    sum: 30
    • 注意:函数定义时还会输出一次

装饰器是一种语法糖

  • 实际上上面的代码等价于
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def add(a, b):
    print "sum: ", a+b

    add = decorator(add)

    # test is a normal
    add()

    # output:
    decorator
    sum: 30

带参数的装饰器

  • 需要对装饰器进一步的封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    def outterDecorator(tag):
    def decorator(func):
    def wrapper(*args, **kwargs):
    print "decorator: " + tag
    return func(*args, **kwargs)
    return wrapper
    return decorator

    @outterDecorator(tag="123")
    def test():
    print "inner test"

    @outterDecorator("abc")
    def add(a, b):
    print "sum: ", a+b

    test()

    add(10, 20)
    # output
    decorator: 123
    inner test
    decorator: abc
    sum: 30
    • 在原始的装饰器外面封装一层函数,用于接受参数,其他的不用改变
  • 理解:

    • 等价于给装饰器加了一层接受参数的外层空间
    • 实际上调用的时候除了参数外,其他的都没变
    • 被装饰的函数依然是被作为内层函数的参数传入装饰器中

类装饰器

  • 类装饰器的简单示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Foo(object):
    def __init__(self, func):
    self._func = func

    def __call__(self):
    print ('class decorator runing')
    self._func()
    print ('class decorator ending')

    @Foo
    def bar():
    print ('bar')

    bar()
    # output
    class decorator runing
    bar
    class decorator ending
  • 如上述代码所示,类装饰器必须有__init____call__两个函数

  • __init__负责接受被装饰函数作为参数并存储该函数

  • __call__负责执行函数调用过程并执行想要插入函数的代码

  • 被装饰的函数被调用时本质上是__call__函数被调用

类装饰器的优点

  • 灵活度高
  • 高内聚,不像函数一样定义在外面
  • 封装的好,容易阅读

多个装饰器的顺序问题

1
2
3
4
5
@a
@b
@c
def f ():
pass
  • 函数可以同时被多个装饰器修饰
  • 装饰器的顺序从靠近函数的那个开始从内向外一层层封装
    1
    f = a(b(c(f)))

装饰器对原始函数的属性修改

  • 涉及到docstring,__name__等属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 装饰器
    def logged(func):
    def with_logging(*args, **kwargs):
    print func.__name__ # 输出 'with_logging'
    print func.__doc__ # 输出 None
    return func(*args, **kwargs)
    return with_logging

    # 函数
    @logged
    def f(x):
    """does some math"""
    return x + x * x

    logged(f)
  • 使用functools.warps装饰器可以修复原始函数的文档

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from functools import wraps
    def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
    print func.__name__ # 输出 'f'
    print func.__doc__ # 输出 'does some math'
    return func(*args, **kwargs)
    return with_logging

    @logged
    def f(x):
    """does some math"""
    return x + x * x

property装饰器

  • 用于类的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Student(object):
    def __init__(self, birth):
    self._birth = birth

    @property
    def birth(self):
    return self._birth

    @birth.setter
    def birth(self, value):
    self._birth = value

    @property
    def age(self):
    return 2014 - self._birth:
  • 当加上property装饰器后,函数就变成了一个只读属性,被修饰的函数不能再当成普通函数

    • 当前函数不能有参数,除非是默认参数,因为当前函数变成属性后,直接调用

      1
      s.birth(10)
      • 解析是s.birth返回一个属性值,然后,属性值不能被调用,所以抛出异常
  • property装饰器会生成两个新的装饰[method_name].setter[method_name].getter,分别用于代表当前函数对应属性的的写和读功能,读的功能默认加上了,写的功能需要的话我们可以使用[method_name].setter装饰器实现

  • 总结: property装饰器可以将类的某个属性封装起来(在不暴露类属性的情况下提供getter方法和setter方法(后者需要自己显示定义))