Python装饰器
装饰器来自 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
同学你好
带参数的装饰器
之前的装饰器是在每次执行函数前打印 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()
用函数来能实现的功能为什么需要类来实现?
因为通过类我们可以将执行过程拆解到各函数中,降低代码的复杂度,甚至可以通过类属性实现一些复杂功能。
比如说我们有一些计算耗时很长的函数,并且每次计算的结果不变
,那么我们就可以通过类定义一个缓存装饰器,来缓存第一次执行的结果。
作者:三眼鸭的编程教室 链接:https://www.zhihu.com/question/26930016/answer/1904166977 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 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秒