Python中的装饰器是一个用于修改类或者函数功能的可调用对象(callable),函数或者实现了__call__
方法的类都可以看作是可调用对象。Python中装饰器分为两大类:
函数装饰器
类装饰器
Python中最简单的装饰器是一个嵌套函数。举例,使用装饰器函数elapsed
来统计函数执行耗时:
# _*_ coding=utf8 _*_ import time import logging logging.basicConfig(level=logging.INFO) def elapsed(func): """统计函数执行耗时""" def wrapper(): start_time = time.time() func() end_time = time.time() logging.info(f'exec {func.__name__} elapsed:{end_time-start_time}') return wrapper def delay(): logging.info('delay...') time.sleep(0.2) # 装饰器就是普通函数,可以像普通函数那样直接调用 elapsed(delay)()
输出如下:
INFO:root:delay... INFO:root:exec delay elapsed:0.20386290550231934
对于装饰器Python在语言层面给予了支持,对上面代码做如下修改:
@elapsed def delay(): logging.info('delay...') time.sleep(0.2) delay()
输出入下:
INFO:root:delay... INFO:root:exec delay elapsed:0.21143507957458496
使用装饰器后,打印函数信息,无法输出原函数信息,执行print(delay)
,输出<function elapsed.<locals>.wrapper at 0x0000018E8AFA4E50>
。
这里可以使用functools
模块来解决:
import functools def elapsed(func): """统计函数执行耗时""" @functools.wraps(func) def wrapper(): start_time = time.time() func() end_time = time.time() logging.info(f'exec {func.__name__} elapsed:{end_time-start_time}') return wrapper
再次执行print(delay)
,输出<function delay at 0x00000186651B4E50>
。
装饰器可以被多次使用:
@elapsed @elapsed def delay(): logging.info('delay...') time.sleep(0.2) delay()
输出如下:
INFO:root:delay... INFO:root:exec delay elapsed:0.21126818656921387 INFO:root:exec wrapper elapsed:0.21126818656921387
上述代码等价于如下调用方式:
def delay(): logging.info('delay...') time.sleep(0.2) # 装饰器就是普通函数,可以像普通函数那样直接调用 # 这里多次调用装饰器函数 elapsed(elapsed(delay))()
调用堆栈如下:
""" logging.info() # wrapper2 logging.info() # wrapper1 logging.info() # delay delay() wrapper1() # func=delay wrapper2() # func=wrapper1 elapsed(wrapper1) # 返回新的wrapper,其中func指向wrapper1,这里记录为wrapper2 elapsed(delay) # 返回wrapper,其中func指向delay函数,这里记录为wrapper1 """
上节中提到的较为简单的装饰器是一个嵌套函数,带有参数的装饰器也是嵌套函数,只不过多嵌套一层:
# _*_ coding=utf8 _*_ import logging import functools logging.basicConfig(level=logging.DEBUG) def exec(count): def exec_wrapper(func): @functools.wraps(func) def func_wrapper(): for i in range(count): logging.info(f'exec {func}') func() return func_wrapper return exec_wrapper @exec(3) def func(): pass func()
输出如下:
INFO:root:exec <function func at 0x000001A783724E50> INFO:root:exec <function func at 0x000001A783724E50> INFO:root:exec <function func at 0x000001A783724E50>
上述示例中的无法直接给函数func
传递参数,否则会抛异常:TypeError: func() takes 0 positional arguments but 1 was given
,因为加上装饰器后,再调用func
相当于调用func_wrapper
函数。想要给原函数传递参数,需对装饰器做如下改造:
def exec(count): def exec_wrapper(func): @functools.wraps(func) def func_wrapper(*args, **kwargs): for i in range(count): logging.info(f'exec {func}') func(*args, **kwargs) return func_wrapper return exec_wrapper
在func_wrapper
函数中添加*args
、**kwargs
两个参数,也可根据需要来添加其中某一个。
类装饰器与函数装饰器类似,只是类装饰器中要实现__call__
方法:
class Elapsed: def __init__(self, func): self.__func = func def __call__(self, *args, **kwargs): start_time = time.time() self.__func(*args, **kwargs) end_time = time.time() logging.info( f'exec {self.__func.__name__} elapsed:{end_time-start_time}') @Elapsed def func(secs=0.1): time.sleep(secs)
装饰器不仅可以用于函数上,也可以用在类上,这里以类装饰器为例:
# _*_ coding=utf8 _*_ import logging import time logging.basicConfig(level=logging.DEBUG) class LogClassName: def __init__(self, cls): self.__cls = cls def __call__(self, *args, **kwargs): logging.info(f'current class name: {self.__cls.__name__}') return self.__cls(*args, **kwargs) def __str__(self): return f'{self.__cls}' @LogClassName class Info: pass logging.info(Info) Info()
Python中的装饰器和装饰器模式有着相同的目的:在不修改原有功能代码的基础上对其做扩展。
Python在语言层面对与装饰器给与了支持,相对比较简洁,经典的装饰器模式在编码实现上通常比Python装饰器有更多的代码量。Python装饰器要明确的作用域某个函数或类上,装饰器模式则是针对某种类型的方法做扩展,具体扩展的对象在运行时才确定。此外,装饰器模式可以作为面向对象中继承的替代。
二者有相同的目的,但实现方式不同,Python装饰器可以看作是静态扩展,装饰器模式是动态扩展。
What is the difference between Python decorators and the decorator pattern?