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
,对实例属性操作的时候,该属性很可能不是直接暴露的,而是通过 getter
和 setter
方法来实现的。
只定义 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. 参考资料
详解Python的装饰器
使用@property
Python 装饰器入门(上)