Python——装饰器decorator

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


简单装饰器

  • 装饰器定义

    1
    2
    3
    4
    5
    def decorator(func):
    def wrapper(*args, **kwargs):
    print "decorator"
    return func(*args, **kwargs)
    return wrapper
  • 装饰器使用

    • 不带参数的函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @decorator
      def test():
      print "inner test"

      # just like a normal function
      test()

      # output:
      decorator
      inner test
    • 带参数的函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @decorator
      def add(a, b):
      print "sum: ", a+b

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

      # output:
      decorator
      sum: 30

装饰器是一种语法糖

  • 实际上上面的代码等价于
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def test():
    print "inner test"

    test = decorator(test)

    # test is a normal
    test()

    # output:
    decorator
    inner test

带参数的装饰器

  • 需要对装饰器进一步的封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
* 在原始的装饰器外面封装一层函数,用于接受参数,其他的不用改变
  • 理解:
    • 等价于给装饰器加了一层接受参数的外层空间
    • 实际上调用的时候除了参数外,其他的都没变
    • 被装饰的函数依然是被作为内层函数的参数传入装饰器中

类装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()
  • 如上述代码所示,类装饰器必须有__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方法(后者需要自己显示定义))