一、装饰器的功能
装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的情况下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同的代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
二、装饰器的两种调用方式
1、早期给函数或对象添加额外的功能
def say_hello():
print('hello')
def debug(func):
def wrapper():
print('[DEBUG]:enter {}()'.format(func.__name__))
return func()
return wrapper
say_hello = debug(say_hello) # 给say_hello函数添加功能(输出debug),并且保持原函数名不变
if __name__ == '__main__':
say_hello()
2、后期的@语法糖
def debug(func):
def wrapper():
print('[DEBUG]:enter {}()'.format(func.__name__))
return func()
return wrapper
@debug
def say_hello():
print('hello')
if __name__ == '__main__':
say_hello()
二、初级装饰器(带参数)
Python的装饰器是一个固定的函数接口形式。它要求你的装饰器函数(或装饰器类)必须接受一个函数并返回一个函数。
'''
def wrapper(func1):
return func2
# 调用方式一,直接包裹
def target_func(args):
pass
result = wrapper(target_func)(args)
# 调用方式二,使用@语法,等同于方式一
@wrapper
def target_func(args):
pass
result = target_func()
'''
具体例子:指定装饰器函数wrapper接受和原函数一样的参数。
def debug(func):
def wrapper(something):
print('[DEBUG]:enter {}()'.format(func.__name__))
return func(something)
return wrapper
def say_hello(something):
print('hello, {}'.format(something))
# 调用方式一(早期)
say_hello = debug(say_hello)
if __name__ == '__main__':
say_hello('C++')
# 调用方式二(@语法糖)
@debug
def say_hello(something):
print('hello, {}'.format(something))
if __name__ == '__main__':
say_hello('C++')
三、高级装饰器
在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。(参见Python的闭包)
1、带参数的装饰器
不仅需要能在进入某个函数后打出log信息,而且还需指定log的级别。这需要用到带参数的装饰器。
def logging(level):
def wrapper_(func):
def wrapper(*args, **kwargs):
print('[{level}]:enter function {func}()'.format(level=level, func=func.__name__))
return func(*args, **kwargs)
return wrapper
return wrapper_
@logging(level='INFO') # @logging(level='DEBUG'),返回的结果是一个装饰器。它其实是一个函数,会马上被执行。
def say(something):
print('say {}'.format(something))
@logging(level='DEBUG')
def do(something):
print('do {}'.format(something))
# 不用语法糖
# say_result = logging(level='INFO')(say)('hello')
# do_result = logging(level='do')(do)('homework')
# 语法糖
if __name__ == '__main__':
say('hello')
do('homework')
2、基于类实现的装饰器
回到装饰器的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么我们也可以用类来实现。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。
class logging(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('[DEBUG]: enter function {func}()'.format(func=self.func.__name__))
return self.func(*args, **kwargs)
@logging
def say(something):
print('say {}'.format(something))
# 不用语法糖调用
# log = logging(func=say)('hello')
if __name__ == '__main__':
say('hello')
----------------------------------------------------callable对象------------------------------------------------
在Python中,callable对象指的是可以像函数一样被调用的对象。具体来说,只要一个对象实现了__call__()方法,就可以将其视为callable对象。当我调用一个类的实例时,实际上是调用了它的__call__方法。除了函数之外,Python中还有很多其他类型的callable对象,比如:
(1)类:如果一个类实现了__call__()方法,那么它的实例也可以向函数一样被调用。
(2)实现了__call__()方法的对象:任何实现了__call__()方法的对象都可以被视为callable对象。这种情况比较少见。
(3)内置函数和内置方法:Python中的一些内置函数和内置方法也是callable对象。例如,abs()、len() str()等函数都是callable对象。
(4)函数对象:函数本身也是callable对象,因为它们实现了__call__()方法。
3、带参数的类装饰器
通过类实现带参数的装饰器时,在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后重载__call__方法时需要接受一个函数并返回一个函数。
class logging(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func):
def wrapper(*args, **kwargs):
print('[{level}]: enter function {func}()'.format(level=self.level, func=func.__name__))
func(*args, **kwargs)
return wrapper
@logging(level='INFO')
def say(something):
print('say {}'.format(something))
@logging(level='DEBUG')
def do(something):
print('do {}'.format(something))
if __name__ == '__main__':
say('hello')
do('homework')
4、内置装饰器
内置装饰器是指一些装饰器函数,可以直接使用而无需自己编写。这些内置装饰器通常用于实现一些通用的功能,例如缓存、计时、类型检查等。
常见的内置装饰器有:
@staticmethod:用于将一个方法定义为静态方法,即不需要使用类实例来调用该方法,可以直接使用类名调用。
@classmethod:用于将一个方法定义为类方法,即可以使用类名调用该方法,并且该方法可以访问类的属性和方法。
@property:用于将一个方法定义为属性,即可以通过实例对象访问该方法,就像访问一个属性一样。
@abstractmethod:用于将一个方法定义为抽象方法,即该方法必须在子类中被实现,否则会抛出异常。
@wraps:用于将一个装饰器函数的元信息(如函数名、文档字符串等)复制到被装饰的函数中,以便于调试和文档生成。
四、装饰器的应用场景
1、函数执行时间的统计
import time
def time_it(func):
def wrapper():
start = time.time()
func()
end = time.time()
print("用时:{}秒".format(end-start))
return wrapper
@time_it
def func1():
time.sleep(2)
print('func1 is running...')
if __name__ == '__main__':
func1()
2、输出日志信息(使用logging模块控制日志输出)
# 1)生成一个日志记录器,并配置日志等级
import logging
# 获取日志记录器,并配置日志等级
logger = logging.getLogger(__name__)
logger.setLevel('DEBUG')
# 2) 配置日志格式、增加handler控制输出流
# 默认日志格式
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(message)s')
# 输出到控制台的handler
chlr = logging.StreamHandler()
# 配置默认日志格式
chlr.setFormatter(formatter)
# 3) 最后将此handler所需要处理的日志等级,没有设置则默认使用logger自身的Level,即DEBUG等级。
# 日志记录器增加此handler
logger.addHandler(chlr)
def log(func):
def inner(*args, **kwargs):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
res = func(*args, **kwargs)
logger.debug(f'func: {func.__name__} {args}->{res}')
return res
return inner
@log
def add(num1, num2):
res = num1 + num2
return res
if __name__ == '__main__':
add(1, 2)
五、python中的装饰器、Java中的注解(Annotation)、C#中的特性(Attribute)之间的区别。
装饰器:装饰器的理念是对原函数、对象的加强,相当于重新封装,所以一般装饰器函数都被命名为wrapper(),意义在于包装。函数只有在被调用时才会发挥其作用。比如@logging装饰器可以在函数执行时额外输出日志,@cache装饰过的函数可以缓存计算结果等等。
注解+特性:注解和特性是对目标函数或对象添加一些属性,相当于将其分类。这些属性可以通过反射拿到,在程序运行时对不同的特性函数或对象加以干预。比如带有Setup的函数就当成准备步骤执行,或者找到所有带有TestMethod的函数依次执行等等。