当前位置: 首页 > 科技观察

Python函数装饰器高级用法

时间:2023-03-11 20:37:26 科技观察

本文转载自微信公众号“东方儿”,作者东方儿。转载本文请联系东方儿公众号。了解了Python函数装饰器和闭包的基础知识后,正式开始学习函数装饰器。典型的函数装饰器下面的例子定义了一个输出函数运行时间的装饰器:函数装饰器与闭包紧密结合,入参func代表被装饰的函数,绑定自由变量后调用函数,结果被返回。使用时钟装饰器:importtimefromclockdecoimportclock@clockdefsnooze(seconds):time.sleep(seconds)@clockdefactorial(n):return1ifn<2elsen*factorial(n-1)if__name__=='__main__':print('*'*40,'Callingsnooze(.123)')snooze(.123)print('*'*40,'Callingfactorial(6)')print('6!=',factorial(6))#6!指的是6的阶乘输出:这是装饰器的典型行为:用一个新函数替换装饰函数,该函数采用相同的参数并返回装饰函数本应返回的值,加上一些额外的操作。值得注意的是factorial()是一个递归函数。从结果来看,每次递归都使用了装饰器并打印了运行时间。这是因为下面的代码:@clockdefactorial(n):return1ifn<2elsen*factorial(n-1)等价于:deffactorial(n):return1ifn<2elsen*factorial(n-1)factorial=clock(factorial)factorialrefers到clock(factorial)函数的返回值,也就是装饰器的内部函数clocked,每次调用factorial(n)都会执行clocked(n)。堆叠装饰器@d1@d2deff():print("f")等价于:deff():print("f")f=d1(d2(f))参数化装饰器如何让装饰器接受参数呢?答案是:创建一个装饰器工厂函数,给它传递参数,返回一个装饰器,然后应用到要装饰的函数上。示例如下:registry=set()defregister(active=True):defdecorate(func):print('runningregister(active=%s)->decorate(%s)'%(active,func))ifactive:registry.add(func)else:registry.discard(func)returnfuncreturndecorate@register(active=False)deff1():print('runningf1()')#注意这里的调用@register()deff2():print('runningf2()')deff3():print('runningf3()')寄存器是一个装饰器工厂函数,它接受一个可选参数active并且默认为True,定义一个装饰器decorate并返回它。需要注意的是,装饰器工厂函数即使不传参,也必须带括号调用,比如@register()。再看一个例子:importtimeDEFAULT_FMT='[{elapsed:0.8f}s]{name}({args})->{result}'#装饰器工厂函数defclock(fmt=DEFAULT_FMT):#真正的装饰器defdecorate(func):#封装装饰函数defclocked(*_args):t0=time.time()#_result是装饰函数返回的真实结果_result=func(*_args)elapsed=time.time()-t0name=func.__name__args=','.join(repr(arg)forargin_args)result=repr(_result)#**locals()返回时钟局部变量print(fmt.format(**locals()))return_resultreturnclockedreturndecorateif__name__=='__main__':@clock()defsnooze(seconds):time.sleep(seconds)foriinrange(3):snooze(.123)这个是给典型的函数装饰器加上参数fmt,装饰器工厂函数加了一层embedding示例中有3个定义。Python标准库中的装饰器内置了三个装饰方法的函数:property、classmethod和staticmethod,以后的文章会讲到。本文介绍了functools中的三个装饰器:functools.wraps、functools.lru_cache和functools.singledispatch。functools.wrapsPython函数装饰器实现时,被装饰的函数实际上是另一个函数(函数名等函数属性会发生变化)。为了不影响它,Python的functools包提供了一个叫做wrapsimplementer的装饰来消除这种副作用(它保留了原函数的名称和函数属性)。示例,没有包装:defmy_decorator(func):defwrapper(*args,**kwargs):'''decorator'''print('Callingdecoratedfunction...')returnfunc(*args,**kwargs)returnwrapper@my_decoratordefexample():"""Docstring"""print('Calledexamplefunction')print(example.__name__,example.__doc__)#outputwrapperdecoratorpluswraps:importfunctoolsdefmy_decorator(func):@functools.wraps(func)defwrapper(*args,**kwargs):'''decorator'''print('Callingdecoratedfunction...')returnfunc(*args,**kwargs)returnwrapper@my_decoratordefexample():"""Docstring"""print('Calledexamplefunction')print(example.__name__,example.__doc__)#OutputexampleDocstringfunctools.lru_cachelru是LeastRecentlyUsed的缩写。是一种优化技术,在传入相同参数时保存耗时函数的结果,避免重复计算。示例:importfunctoolsfromclockdecoimportclock@functools.lru_cache()@clockdefbonacci(n):ifn<2:returnnreturnfibonacci(n-2)+fibonacci(n-1)if__name__=='__main__':print(fibonacci(6))优化递归算法,执行时间减半。请注意,可以使用两个可选参数配置lru_cache。其签名如下:functools.lru_cache(maxsize=128,typed=False)maxsize:最大存储量,当缓存满时,旧结果将被丢弃。typed:如果设置为True,将分别保存不同参数类型的结果,即区分通常认为相等的浮点数和整型参数(如1和1.0)。functools.singledispatchPython3.4的新语法可以用来优化函数中的大量if/elif/elif。用@singledispatch装饰的普通函数变成通用函数:一组以不同方式执行相同操作的函数,具体取决于第一个参数的类型。所以叫singledispatch、singledispatch。根据多个参数进行调度就是多调度。示例,生成HTML,显示不同类型的Python对象:importhtmldefhtmlize(obj):content=html.escape(repr(obj))return'

{}
'.format(content)因为Python不支持repr加载方法或函数,所以不能使用不同的签名来定义htmlize的变体,只能把htmlize变成一个派发函数,使用if/elif/elif,调用特殊的函数,比如htmlize_str、htmlize_int等。届时,htmlize会变得很大,与各个专业功能的耦合也很紧,不方便模块扩展。@singledispatch经过深思熟虑后被添加到标准库中以解决此类问题:fromfunctoolsimportsingledispatchfromcollectionsimportabcimportnumbersimporthtml@singledispatchdefhtmlize(obj):#这里的基本函数不需要写if/elif/elif来dispatchcontent=html.escape(repr(obj)))return'
{}
'.format(content)@htmlize.register(str)def_(text):#specialfunctioncontent=html.escape(text).replace('\n','
\n')return'

{0}

'.format(content)@htmlize.register(numbers.Integral)def_(n):#specialfunctionreturn'
{0}(0x{0:x})
'.format(n)@htmlize.register(tuple)@htmlize.register(abc.MutableSequence)def_(seq):#specialfunctioninner='\n
  • '.join(htmlize(item)foriteminseq)return'
      \n
    • '+inner+'
    • \n
    '@singledispatch修饰基函数。特殊函数用@<>.register(<>)修饰。它的名字并不重要,取名为_,简单明了。这样写完代码后,Python会根据第一个参数的类型调用相应的专门函数。总结本文首先介绍一个典型的函数装饰器:用一个新的函数替换被装饰的函数,两者都接受相同的参数,返回被装饰函数应该返回的值,同时做一些额外的操作。然后介绍装饰器的两种高级用法:堆叠装饰器和参数化装饰器,它们都增加了函数的嵌套层次。最后介绍标准库中的三个装饰器:保留函数原有属性的functools.wraps、缓存函数耗时结果的functools.lru_cache、优化if/elif/elif代码的functools.singledispatch。参考资料:《流畅的Python》https://github.com/fluentpython/example-code/tree/master/07-closure-decohttps://blog.csdn.net/liuzonghao88/article/details/103586634