装饰器的实现
任何可调用对象(任何实现了call方法的对象都是可调用的)都可以用作装饰器,他们返回的对象往往是实现了自己的call方法的更复杂的类的实例
装饰器使用语法
装饰器本质也是一个函数,其返回值就是一个函数对象
使用方法:
1.先定义一个装饰函数(或者类或者偏函数实现)
2.再定义业务函数或者类
3.将业务函数作为参数传入装饰函数
装饰器通用模板
函数装饰器通用模板:
def mydecorator(function):def wrapped(*args, **kwargs):# 在调用原始函数之前,做点什么result = function(*args, **kwargs)# 在函数调用之后,做点什么# 并返回结果return result# 返回 wrapper 作为装饰函数return wrapped
类装饰器通用模板:如果装饰器需要复杂的参数化或者依赖于特定状态,使用用户自定义类可能更好
class DecoratorAsClass:def __init__(self, function):self.function = functiondef __call__(self, *args, **kwargs):# 调用原始函数前,做点什么result = self.function(*args, **kwargs)# 调用函数之后,做点什么# 并返回结果return result
装饰器简单应用
装饰器应用:
- 数据验证
- 日志
- 缓存
- 监控
- 调试
- 业务规则
- 加密
1.日志打印器
2.时间计时器
# 日志打印器def logger(func):def wrapper(*args, **kwargs):print("我准备开始计算:{}函数了".format(func.__name__))func(*args, **kwargs)print("我已执行完函数了!!!")return wrapper@loggerdef add(x, y):print('{} + {} = {}'.format(x, y, x+y))add(1, 2)----------------#我准备开始计算:add函数了#1 + 2 = 3#我已执行完函数了!!!
# 时间计时器def timer(func):def wrapper(*args, **kwargs):t1 = time.time()func(*args, **kwargs)t2 = time.time()result_time = t2 - t1print("执行函数一共花了{}".format(result_time))return wrapperimport time@timerdef want_sleep(sleep_time):time.sleep(sleep_time)want_sleep(10)----------------------#执行函数一共花了10.008701086044312
带参数的函数装饰器
use_logging 是一个带参数的装饰器,我们可以理解为它是一个含有参数的闭包,当我们使用@use_logging(level=’warn’),
将业务函数foo1作为参数传入,传递到装饰器中
# 带参数的装饰器def use_logging(level):def decorator(func):def wrapper(*args, **kwargs):if level == 'warn':print("warning !!!!!,{} is running".format(func.__name__))if level == 'info':print("{} is running".format(func.__name__))return func(*args, **kwargs)return wrapperreturn decorator@use_logging(level='warn')def foo1(name='foo'):print("i am {}".format(name))@use_logging(level='info')def foo2(name='FOO'):print("i am {}".format(name))foo1()foo2()----------#warning !!!!!,foo1 is running#i am foo#foo2 is running#i am FOO
不带参数的类装饰器
实现类装饰器,必须实现__call,和 _init两个内置函数,
init:用于接收被装饰的函数,
call:用于实现装饰逻辑
class logger:def __init__(self, func):self.func = funcdef __call__(self, *args, **kwargs):print("[INFO]:the function {func}() is running".format(func=self.func.__name__))return self.func(*args, **kwargs)@loggerdef say(name):print("say {}!".format(name))say("zaygee")---------------------#[INFO]:the function say() is running#say zaygee!
带参数的类装饰器
实现带参数的类装饰器,必须实现__call,和 _init两个内置函数,
init:用于接收传入参数
call:用于接收被装饰函数+实现装饰逻辑
# 带参数的类装饰器class loggering(object):def __init__(self, level='INFO'):"""接收传入参数"""self.level = leveldef __call__(self, func):"""接收函数并返回函数"""def warpper(*args, **kwargs):print("[{level}]: the function {func}() is runnig".format(level=self.level, func=func.__name__))func(*args, **kwargs)return warpper@loggering(level='WARNNING')def say(name):print("say {name}".format(name=name))say("i am zaygee")------------------------#[WARNNING]: the function say() is runnig#say i am zaygee
# 封装http请求日志打印class Logger:"""带参数的类装饰器:http日志输出"""def __init__(self, method):self.method = methoddef __call__(self, func):def logging(*args, **kwargs):logger.info(f"用例开始执行{func.__name__}")res = func(*args, **kwargs)if self.method.lower() == 'post':logger.info(f'请求URl:{res.url}')else:logger.info('不是post请求')logger.info(f"用例结束执行{func.__name__}")return loggingclass BaseRequest:headers = {'Accept': 'application/json, text/javascript, */*; q=0.01','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ''Chrome/81.0.4044.122 Safari/537.36','Connection': 'keep-alive','Content-Type': 'application/json'}def __init__(self):self.res = requests.session()self.res.headers = self.headers@Logger(method='post')def base_post(self, url, data=None, json=None, **kwargs):res = self.res.post(url=url, data=data, json=json, verify=True, **kwargs)return res2021-07-11 22:51:55.818 | INFO | __main__:case_logging:105 - 用例开始执行2021-07-11 22:51:55.818 | INFO | __main__:case_logging:107 - 1*4 = 42021-07-11 22:51:55.818 | INFO | __main__:case_logging:108 - 用例执行结束2021-07-11 22:51:55.819 | INFO | __main__:logging:229 - 用例开始执行base_post2021-07-11 22:51:56.058 | INFO | __main__:logging:232 - 请求URl:https://web/accounts/login2021-07-11 22:51:56.059 | INFO | __main__:logging:235 - 用例结束执行base_post
wraps装饰器
@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
装饰器极大的复用了代码,但是唯一的缺点就是原函数的元信息不见了 ,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了
# functions.wrapsfrom functools import wrapsdef my_decorator(func):@wraps(func)def wrapper(*args, **kwargs):print("calling decorated function")return func(*args, **kwargs)return wrapper@my_decoratordef example():"""Dcostring"""print("called decorated function")example()print(example.__name__)print(example.__doc__)-------------#calling decorated function#called decorated function#example# Dcostring
内置装饰器property
通常存在于类中,可以将函数定义成一个属性,属性的值就是该函数的返回,
当我们需要对属性做合法性的校验,可以考虑使用proprety装饰器
class C:def __init__(self):self._x = None@propertydef x(self):"""i am the 'x' property"""return self._x@x.setterdef x(self, value):self._x = value@x.deleterdef x(self):del self._xc = C()c.x = 10 # 设置属性print(c.x) # 查看属性del c.x # 删除属性-------------#10
例子:校验手机号的合法性
class Phone:def __init__(self):self._x = None@propertydef x(self):"""i am the 'x' property"""return self._x@x.setterdef x(self, value):if not isinstance(value, int):# raise ValueError("请输入合法手机号")print("请输入合法手机号")else:self._x = valueprint("输入的手机:{}是合法的".format(self._x))@x.deleterdef x(self):del self._xsj = Phone()sj.x = 'dfdfd'del sj.xsj.x = 13838839333----------------#请输入合法手机号#输入的手机:13838839333是合法的
多装饰器装饰的执行顺序
执行顺序为:至上而下,再至下而上 执行
from logzero import loggerimport time# here put the import libdef my_decorator(func):def wrapper(*args, **kwargs):logger.info(f"调用业务代码前执行")result = func(*args, **kwargs)logger.info(f"调用业务代码后执行")return resultreturn wrapperdef decorator_time(func):def wrapper(*args, **kwargs):start = time.time()logger.info(f"开始执行: {start}")result = func(*args, **kwargs)logger.info(f"执行代码耗时: {time.time()-start}")return resultreturn wrapperdef decorator_with_params(params):def decorator(func):def wrapper(*args, **kwargs):if params != 1:logger.info("params=1")else:logger.info("params is not eq 1")return func(*args, **kwargs)logger.info("带参数decorator执行完毕")return wrapperreturn decorator@decorator_time@my_decorator@decorator_with_params(1)def test_decorator():logger.info("执行业务代码")if __name__ == "__main__":test_decorator()"""[I 220112 23:30:23 test1:23] 开始执行: 1642001423.521113[I 220112 23:30:23 test1:13] 调用业务代码前执行[I 220112 23:30:23 test1:35] params is not eq 1[I 220112 23:30:23 test1:46] 执行业务代码[I 220112 23:30:23 test1:15] 调用业务代码后执行[I 220112 23:30:23 test1:25] 执行代码耗时: 0.00032520294189453125"""
使用decorator轻松实现装饰器
from decorator import decorator# @decorator# def decoration(func, *args, **kwargs):# """decorator装饰 生成一个装饰器函数"""# print("Ready to run func " + func.__name__)# func(*args, **kwargs)# print("successful to run func " + func.__name__)# 等同于如下实现def decoration(func, *args, **kwargs):def wrapper(*args, **kwargs):print("Ready to run func " + func.__name__)func(*args, **kwargs)print("successful to run func " + func.__name__)return wrapper@decorationdef demo():print("run the demo task")demo()print(demo.__name__) # wrapper@decoratordef decoration_with_params(func, values=1, *args, **kwargs):results = func(*args, **kwargs)if values == 1:print('values = 1')return resultselse:print('values ! = 1')return '3'@decoration_with_params(values=1)def demo_2():print("run the demo 2 task")return 1print(demo_2())print(demo_2.__name__) # demo_2
参考自:https://zhuanlan.zhihu.com/p/65968462、https://foofish.net/decorator.html
