本文共 5451 字,大约阅读时间需要 18 分钟。
先来看一个代码段:
def announce(f): def wrapper(name): print("start to run the function...") f(name) print("Done with the function.") return wrapper@announcedef hello(name): print(f"hello, {name}")name = "Sudley"print(f"function name: {hello.__name__}")hello(name)
@announce就是装饰器,通过装饰器可以快速的给函数添加功能。装饰器让你在一个函数的前后去执行代码。
上面是装饰器可以理解为hello=announce(hello),所以hello(name)相当于wrapper(name) 上面查看hello的函数名称是announce中返回的函数名称wrapper,也行你想hello依然是hello可以使用functools.wraps来修饰一下,@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。from functools import wrapsdef announce(f): @wraps(f) def wrapper(name): print("start to run the function...") f(name) print("Done with the function.") return wrapper@announcedef hello(name): print(f"hello, {name}")name = "Sudley"print(f"function name: {hello.__name__}")hello(name)
平常可能会遇到需要定义函数开始运行和介绍运行的日志,如
import logginglogging.basicConfig(level=logging.INFO)def square_func(x): logging.info("square_func was called") return x * xdef add_func(x, y): logging.info("add_func was called") return x + ys_result = square_func(4)a_result = add_func(2, 3)print(s_result, a_result)可以看到上面的logging.info有一点坏味道,代码重复了,这时就可以用到装饰器进行优化,如下
from functools import wrapsimport logginglogging.basicConfig(level=logging.INFO)def logit(func): @wraps(func) def with_called_logging(*args, **kwargs): logging.info(func.__name__ + " was called") return func(*args, **kwargs) return with_called_logging@logitdef square_func(x): return x * x@logitdef add_func(x, y): return x + ys_result = square_func(4)a_result = add_func(2, 3)print(s_result, a_result)
当通过wget从网站上download文件时,也许会因为服务、网络不稳定或者其他原因导致失败,也许是遇到性能瓶颈或者暂时无法找到根因,这时sleep一段时间并retry也许能规避当前问题
import timedef retry(func): def retry_when_fail(*args, **kwargs): retry_times = 5 for times in range(retry_times): if not func(*args, **kwargs): if times != (retry_times - 1): print("run %s failed,retry times %s" % (func.__name__, times)) time.sleep(3) continue else: print("run %s failed.exit" % func.__name__) raise print("run %s success." % func.__name__) break return func(*args, **kwargs) return retry_when_fail@retrydef download_tool(ret): #download sth return retif __name__ == '__main__': #ret = True ret = False download_tool(ret)
ret = True和False的运行演示如下
前面我们用到的@wraps(func)就是带参数的装饰器,允许传入func参数,下面我们定义一个类似的装饰器。在前面日志的基础上允许添加日志级别的参数。
import logginglogging.basicConfig(level=logging.INFO)def logit(level="info"): def decorator(func): def with_called_logging(*args, **kwargs): if level == "info": logging.info(func.__name__ + " was called") elif level == "warn": logging.warn(func.__name__ + " was called") return func(*args, **kwargs) return with_called_logging return decorator@logit()def square_func(x): return x * x@logit("warn")def add_func(x, y): return x + ys_result = square_func(4)a_result = add_func(2, 3)print(s_result, a_result)
这里相比之前多封装了一层函数,可以理解为含有参数的闭包。
由于logit()=decorator 所以@logit()、@logit(“warn”)都等于@decorator,日志级别相关参数通过闭包的形式传入。装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
import logginglogging.basicConfig(level=logging.INFO)def logit(level="info"): class decorator(object): def __init__(self, func, level = "info"): self.level = level self._func = func def __call__(self,*args): if self.level == "info": logging.info(self._func.__name__ + " was called") elif self.level == "warn": logging.warn(self._func.__name__ + " was called") return self._func(*args) return decorator@logit()def square_func(x): return x * x@logit(level = "warn")def add_func(x, y): return x + ys_result = square_func(4)a_result = add_func(2, 3)print(s_result, a_result)
创建类型的时候定义了__call__()方法,这个类型就是可调用的。
import logginglogging.basicConfig(level=logging.INFO)class decorator(object): def __init__(self, func, level = "info"): self.level = level self._func = func def __call__(self,*args): if self.level == "info": logging.info(self._func.__name__ + " was called") elif self.level == "warn": logging.warn(self._func.__name__ + " was called") return self._func(*args)#@decoratordef square_func(x): return x * xsquare_func = decorator(square_func)print(type(square_func))s_result = square_func(4) #类的实例可以直接调用print(s_result)不含__call__方法的类型举例:
import logginglogging.basicConfig(level=logging.INFO)class decorator(object): def __init__(self, func, level = "info"): self.level = level self._func = func def not_call(self,*args): if self.level == "info": logging.info(self._func.__name__ + " was called") elif self.level == "warn": logging.warn(self._func.__name__ + " was called") return self._func(*args)#@decoratordef square_func(x): return x * xsquare_func = decorator(square_func)print(type(square_func))try: s_result = square_func(4)except TypeError as e: print(e)s_result = square_func.not_call(4)print(s_result)
转载地址:http://nyiti.baihongyu.com/