装饰器来自 Decorator
的直译,理解装饰这个词就等于理解了装饰器。
什么叫装饰,就是装点、提供一些额外的点缀。在 python 中的装饰器则是提供了一些额外的功能。
函数装饰器
在学习闭包的时候我们就已经知道,函数是一个对象。
这意味着函数:
- 能在函数中定义一个函数
- 能作为参数传递
- 能作为返回值
来看一个简单的例子。
def decorator(func):
def wrapper(*args, **kwargs):
print('123')
return func(*args, **kwargs)
return wrapper
def say_hello():
print('同学你好')
say_hello_super = decorator(say_hello)
say_hello_super()
结果:
123
同学你好
在这段代码中,我们将一个函数 say_hello
作为参数传入函数 decorator
,返回一个 wrapper
函数并赋值到 say_hello_super
,此时执行 say_hello_super
相当于执行 wrapper
函数。当我们执行 wrapper
函数时会先打印 123
再执行先前传入的 func
参数也就是 say_hello
函数。
注意 wrapper
的 *args
与 **kwargs
参数,这是必须的, *args
表示所有的位置参数,**kwargs
表示所有的关键字参数。之后再将其传到 func
函数中, 这样保证了能完全传递所有参数。
在这里,decorator
这个函数就是一个装饰器,功能是在执行被装饰的函数之前打印 123
。
在 python 中, 有一种语法糖可以代替 say_hello_super = decorator(say_hello)
这一步的操作,以上的代码可以改写成。
def decorator(func):
def wrapper(*args, **kwargs):
print('123')
return func(*args, **kwargs)
return wrapper
@decorator
def say_hello():
print('同学你好')
say_hello()
结果:
123
同学你好
这里在函数前加上 @decorator
相当于在定义函数后执行了一条语句, say_hello = decorator(say_hello)
。
带参数的装饰器
之前的装饰器是在每次执行函数前打印 123
, 如果我们想指定打印的值,那该怎么办?
def info(value):
def decorator(func):
def wrapper(*args, **kwargs):
print(value)
return func(*args, **kwargs)
return wrapper
return decorator
@info('456')
def say_hello():
print('同学你好')
say_hello()
结果:
456
同学你好
我们可以在装饰器外部再套上一层函数,用该函数的参数接收我们想要打印的数据,并将先前的 decorator
函数作为返回值。这就是之前学到的闭包的一种功能,就是用闭包来生成一个命名空间,在命名空间中保存我们要打印的值 value
。
wraps 装饰器
一个函数不止有他的执行语句,还有着 __name__
(函数名),__doc__
(说明文档)等属性,我们之前的例子会导致这些属性改变。
def decorator(func):
def wrapper(*args, **kwargs):
"""doc of wrapper"""
print('123')
return func(*args, **kwargs)
return wrapper
@decorator
def say_hello():
"""doc of say hello"""
print('同学你好')
print(say_hello.__name__)
print(say_hello.__doc__)
结果:
wrapper
doc of wrapper
由于装饰器返回了 wrapper
函数替换掉了之前的 say_hello
函数,导致函数名,帮助文档变成了 wrapper
函数的了。
解决这一问题的办法是通过 functools
模块下的 wraps
装饰器。
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""doc of wrapper"""
print('123')
return func(*args, **kwargs)
return wrapper
@decorator
def say_hello():
"""doc of say hello"""
print('同学你好')
print(say_hello.__name__)
print(say_hello.__doc__)
结果:
say_hello
doc of say hello
内置装饰器
有三种我们经常会用到的装饰器, property
、 staticmethod
、 classmethod
,他们有个共同点,都是作用于类方法之上。
property 装饰器
property
装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。
class XiaoMing:
first_name = '明'
last_name = '小'
@property
def full_name(self):
return self.last_name + self.first_name
xiaoming = XiaoMing()
print(xiaoming.full_name)
结果:
小明
例子中我们像获取属性一样获取 full_name
方法的返回值,这就是用 property
装饰器的意义,既能像属性一样获取值,又可以在获取值的时候做一些操作。
staticmethod 装饰器
staticmethod
装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self
参数,也无法访问实例化后的对象
class XiaoMing:
@staticmethod
def say_hello():
print('同学你好')
XiaoMing.say_hello()
# 实例化调用也是同样的效果
# 有点多此一举
xiaoming = XiaoMing()
xiaoming.say_hello()
同学你好
同学你好
classmethod 装饰器
classmethod
依旧是用于类中的方法,这表示这个方法将会是一个类方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self
参数,也无法访问实例化后的对象。相对于 staticmethod
的区别在于它会接收一个指向类本身的 cls
参数。
class XiaoMing:
name = '小明'
@classmethod
def say_hello(cls):
print('同学你好, 我是' + cls.name)
print(cls)
XiaoMing.say_hello()
结果:
同学你好, 我是小明
<class '__main__.XiaoMing'>
类装饰器
刚刚我们接触的装饰器是函数来完成,实际上由于 python 的灵活性, 我们用类也可以实现一个装饰器。
类能实现装饰器的功能, 是由于当我们调用一个对象时,实际上调用的是它的 __call__
方法。
class Demo:
def __call__(self):
print('我是 Demo')
demo = Demo()
demo()
结果:
我是 Demo
通过这个特性,我们便可以用类的方式来完成装饰器,功能与刚开始用函数实现的一致。
class Decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('123')
return self.func(*args, **kwargs)
@Decorator
def say_hello():
print('同学你好')
say_hello()
结果:
123
同学你好
用函数来能实现的功能为什么需要类来实现?
因为通过类我们可以将执行过程拆解到各函数中,降低代码的复杂度,甚至可以通过类属性实现一些复杂功能。
比如说我们有一些计算耗时很长的函数,并且每次计算的结果不变
,那么我们就可以通过类定义一个缓存装饰器,来缓存第一次执行的结果。
import time
class Cache:
__cache = {}
def __init__(self, func):
self.func = func
def __call__(self):
# 如果缓存字典中有这个方法的执行结果
# 直接返回缓存的值
if self.func.__name__ in Cache.__cache:
return Cache.__cache[self.func.__name__]
# 计算方法的执行结果
value = self.func()
# 将其添加到缓存
Cache.__cache[self.func.__name__] = value
# 返回计算结果
return value
@Cache
def long_time_func():
time.sleep(5)
return '我是计算结果'
start = time.time()
print(long_time_func())
end = time.time()
print(f'计算耗时{end-start}秒')
start = time.time()
print(long_time_func())
end = time.time()
print(f'计算耗时{end-start}秒')
结果:
我是计算结果
计算耗时5.001157283782959秒
我是计算结果
计算耗时0.0秒
这是一篇理解装饰器很棒的文章