装饰器

浏览 905

课文

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

实战

实现一个装饰器,能够计算函数的执行耗时。

解析

```python import time def calc(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f'{func.__name__}函数执行耗时{end-start}秒。') return result return wrapper @calc def count_2(): for i in range(0, 2): print(f'第{i+1}次计数') time.sleep(1) # 暂停一秒钟 @calc def count_5(): for i in range(0, 5): print(f'第{i+1}次计数') time.sleep(1) # 暂停一秒钟 count_2() count_5() ``` ```output 第1次计数 第2次计数 count_2函数执行耗时2.0178287029266357秒。 第1次计数 第2次计数 第3次计数 第4次计数 第5次计数 count_5函数执行耗时5.0245983600616455秒。 ```
点击查看

评论

登录参与讨论

暂无评论

共 0 条
  • 1
前往
  • 1

课程目录

第一课:命名空间与作用域 第二课:闭包 第三课:装饰器 第四课:迭代器 第五课:生成器

学习遇到困难?微信扫码进入社群与小伙伴一起交流讨论。