4. 装饰器

4.1. 作用

装饰器本质上是一个 Python 函数,它可以让其他函数在 不需要做任何代码变动 的前提下 增加额外功能 ,装饰器的返回值也是一个函数对象。 它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

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

4.2. 使用装饰器计时

 1from functools import wraps
 2import time
 3
 4def timer(func):
 5    @wraps(func)
 6    def function_timer(*args, **kwargs):
 7        start_time = time.time()
 8        result = func(*args, **kwargs)
 9        end_time = time.time()
10        print "time (s): ", end_time - start_time
11        return result
12    return function_timer
13
14@timer
15def foo():
16    print "hello"
17    return 0
18
19print foo.__name__
20print foo.__doc__

使用 wraps 可以保持函数 foo() 的属性 __name____doc__ ,而不变成函数 function_timer 的相关属性。

4.3. @property

4.3.1. 暴露属性

1class Student(object):
2
3    def __init__(self, value):
4        self.score = value
1>>> obj = Student(10)
2>>> obj.score
310
4>>> obj.score = -100
5>>> obj.score
6-100

直接把属性暴露出去,虽然写起来很简单,但是没办法检查参数,导致 score 可以被随意改动。

4.3.2. 实例方法

为了限制 score 的范围,可以通过一个 set_score() 方法来设置成绩,再通过一个 get_score() 方法来获取成绩。 这样,在 set_score() 方法里,就可以检查参数。

 1class Student(object):
 2
 3  def __init__(self, value):
 4      self._score = value
 5
 6  def get_score(self):
 7      return self._score
 8
 9  def set_score(self, value):
10      if not isinstance(value, int):
11          raise ValueError('score must be an integer !')
12      if value < 0 or value > 100:
13          raise ValueError('score must between 0 ~ 100 !')
14      self._score = value
1>>> obj = Student(10)
2>>> obj.get_score()
310
4>>> obj.set_score(-100)
5ValueError: score must between 0 ~ 100 !

但是,上面的调用方法又略显复杂,没有直接调用属性那么直接简单。

4.3.3. @property

@property 装饰器负责把一个方法变成属性。

 1class Student(object):
 2
 3  def __init__(self, value):
 4      self._score = value
 5
 6  @property
 7  ## 把一个 getter 方法变成属性
 8  def score(self):
 9      return self._score
10
11  @score.setter
12  ## 把一个 setter 方法变成属性赋值
13  def score(self, value):
14      if not isinstance(value, int):
15          raise ValueError('score must be an integer !')
16      if value < 0 or value > 100:
17          raise ValueError('score must between 0 ~ 100 !')
18      self._score = value
1>>> obj = Student(10)
2>>> obj.score = 60
3>>> obj.score
460
5>>> obj.score = -10
6ValueError: score must between 0 ~ 100 !

利用 @property ,对实例属性操作的时候,该属性很可能不是直接暴露的,而是通过 gettersetter 方法来实现的。

只定义 getter 方法,不定义 setter 方法就是一个只读属性。

4.4. @register

装饰器不是必须要修饰被装饰的函数,它还可以简单地注册一个函数,并将其解包返回。例如,可以使用它来创建一个轻量级插件体系结构:

 1PLUGINS = dict()
 2
 3def register(func):
 4    """Register a function as a plug-in"""
 5    PLUGINS[func.__name__] = func
 6    return func
 7
 8@register
 9def say_hello():
10    print("hello")
11
12@register
13def say_goodbye():
14    print("good bye")
15
16
17if __name__ == "__main__":
18    import random
19    say = random.choice(["say_hello", "say_goodbye"])
20    PLUGINS[say]() ## 调用函数

4.5. 缓存装饰器

LRU,即 Least Recently Used,最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。

lru_cache 根据参数缓存每次函数调用结果,对于相同参数的,无需重新函数计算,直接返回之前缓存的返回值。 maxsize 指定了缓存的长度, typed=True 则不同类型的函数参数将单独缓存,例如,f(3)和f(3.0)将视为不同的调用。

1import functools
2
3@functools.lru_cache(maxsize=128, typed=False)
4def fibonacci(n:int) -> int:
5    if n == 0: return 0
6    elif n == 1: return 1
7    elif n > 1:
8        return fibonacci(n-2) + fibonacci(n-1)

4.6. 参考资料

  1. 详解Python的装饰器

  1. 使用@property

  1. Python 装饰器入门(上)