在了解了Python函数装饰器基础知识和闭包之后,开始正式学习函数装饰器。

典型的函数装饰器

以下示例定义了一个装饰器,输出函数的运行时间:

函数装饰器和闭包紧密结合,入参func代表被装饰函数,通过自由变量绑定后,调用函数并返回结果。

使用clock装饰器:

import time
from clockdeco import clock @clock
def snooze(seconds):
time.sleep(seconds) @clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1) if __name__=='__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6)) # 6!指6的阶乘

输出结果:

这是装饰器的典型行为:把被装饰的函数换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值,同时还会做些额外操作。

值得注意的是factorial()是个递归函数,从结果来看,每次递归都用到了装饰器,打印了运行时间,这是因为如下代码:

@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)

等价于:

def factorial(n):
return 1 if n < 2 else n*factorial(n-1) factorial = clock(factorial)

factorial引用的是clock(factorial)函数的返回值,也就是装饰器内部函数clocked,每次调用factorial(n),执行的都是clocked(n)。

叠放装饰器

@d1
@d2
def f():
print("f")

等价于:

def f():
print("f") f = d1(d2(f))

参数化装饰器

怎么让装饰器接受参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

示例如下:


registry = set() def register(active=True):
def decorate(func):
print('running register(active=%s)->decorate(%s)'
% (active, func))
if active:
registry.add(func)
else:
registry.discard(func) return func
return decorate @register(active=False)
def f1():
print('running f1()') # 注意这里的调用
@register()
def f2():
print('running f2()') def f3():
print('running f3()')

register是一个装饰器工厂函数,接受可选参数active默认为True,内部定义了一个装饰器decorate并返回。需要注意的是装饰器工厂函数,即使不传参数,也要加上小括号调用,比如@register()

再看一个示例:

import time

DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'

# 装饰器工厂函数
def clock(fmt=DEFAULT_FMT):
# 真正的装饰器
def decorate(func):
# 包装被装饰的函数
def clocked(*_args):
t0 = time.time()
# _result是被装饰函数返回的真正结果
_result = func(*_args)
elapsed = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
# **locals()返回clocked的局部变量
print(fmt.format(**locals()))
return _result
return clocked
return decorate if __name__ == '__main__': @clock()
def snooze(seconds):
time.sleep(seconds) for i in range(3):
snooze(.123)

这是给典型的函数装饰器添加了参数fmt,装饰器工厂函数增加了一层嵌套,示例中一共有3个def

标准库中的装饰器

Python内置了三个用于装饰方法的函数:property、classmethod和staticmethod,这会在将来的文章中讲到。本文介绍functools中的三个装饰器:functools.wraps、functools.lru_cache和functools.singledispatch。

functools.wraps

Python函数装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用(它能保留原有函数的名称和函数属性)。

示例,不加wraps:

def my_decorator(func):
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
return func(*args, **kwargs)
return wrapper @my_decorator
def example():
"""Docstring"""
print('Called example function') print(example.__name__, example.__doc__)
# 输出wrapper decorator

加wraps:

import functools

def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
'''decorator'''
print('Calling decorated function...')
return func(*args, **kwargs)
return wrapper @my_decorator
def example():
"""Docstring"""
print('Called example function') print(example.__name__, example.__doc__)
# 输出example Docstring

functools.lru_cache

lru是Least Recently Used的缩写,它是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。

示例:

import functools

from clockdeco import clock

@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(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.singledispatch

Python3.4的新增语法,可以用来优化函数中的大量if/elif/elif。使用@singledispatch装饰的普通函数会变成泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数。所以它叫做single dispatch,单分派。

根据多个参数进行分派,就是多分派了。

示例,生成HTML,显示不同类型的Python对象:

import html

def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)

因为Python不支持重载方法或函数,所以就不能使用不同的签名定义htmlize的变体,只能把htmlize变成一个分派函数,使用if/elif/elif,调用专门的函数,比如htmlize_str、htmlize_int等。时间一长htmlize会变得很大,跟各个专门函数之间的耦合也很紧密,不便于模块扩展。

@singledispatch经过深思熟虑后加入到了标准库,来解决这类问题:

from functools import singledispatch
from collections import abc
import numbers
import html @singledispatch
def htmlize(obj):
# 基函数 这里不用写if/elif/elif来分派了
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content) @htmlize.register(str)
def _(text):
# 专门函数
content = html.escape(text).replace('\n', '<br>\n')
return '<p>{0}</p>'.format(content) @htmlize.register(numbers.Integral)
def _(n):
# 专门函数
return '<pre>{0} (0x{0:x})</pre>'.format(n) @htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
# 专门函数
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'

@singledispatch装饰了基函数。专门函数使用@<<base_function>>.register(<<type>>)装饰,它的名字不重要,命名为_,简单明了。

这样编写代码后,Python会根据第一个参数的类型,调用相应的专门函数。

小结

本文首先介绍了典型的函数装饰器:把被装饰的函数换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值,同时还会做些额外操作。接着介绍了装饰器的两个高级用法:叠放装饰器和参数化装饰器,它们都会增加函数的嵌套层级。最后介绍了3个标准库中的装饰器:保留原有函数属性的functools.wraps、缓存耗时的函数结果的functools.lru_cache和优化if/elif/elif代码的functools.singledispatch。

参考资料:

《流畅的Python》

https://github.com/fluentpython/example-code/tree/master/07-closure-deco

https://blog.csdn.net/liuzonghao88/article/details/103586634

Python函数装饰器高级用法的更多相关文章

  1. Python @函数装饰器及用法

    1.函数装饰器的工作原理 函数装饰器的工作原理是怎样的呢?假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示: #funA 作为装饰器函数 def funA(fn): #... fn ...

  2. Python @函数装饰器及用法(超级详细)

    函数装饰器的工作原理是怎样的呢?假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示: #funA 作为装饰器函数 def funA(fn): #... fn() # 执行传入的fn参 ...

  3. Python函数装饰器原理与用法详解《摘》

    本文实例讲述了Python函数装饰器原理与用法.分享给大家供大家参考,具体如下: 装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值 ...

  4. python函数-装饰器

    python函数-装饰器 1.装饰器的原则--开放封闭原则 开放:对于添加新功能是开放的 封闭:对于修改原功能是封闭的 2.装饰器的作用 在不更改原函数调用方式的前提下对原函数添加新功能 3.装饰器的 ...

  5. Python的装饰器实例用法小结

    这篇文章主要介绍了Python装饰器用法,结合实例形式总结分析了Python常用装饰器的概念.功能.使用方法及相关注意事项 一.装饰器是什么 python的装饰器本质上是一个Python函数,它可以让 ...

  6. Python 函数装饰器

    首次接触到装饰器的概念,太菜啦! Python 装饰器可以大大节省代码的编写量,提升代码的重复使用率.函数装饰器其本质也是一个函数,我们可以把它理解为函数中定义了一个子函数. 例如我们有这么一个需求, ...

  7. Python高手之路【四】python函数装饰器

    def outer(func): def inner(): print('hello') print('hello') print('hello') r = func() print('end') p ...

  8. python 函数 装饰器 内置函数

    函数 装饰器 内置函数 一.命名空间和作用域 二.装饰器 1.无参数 2.函数有参数 3.函数动态参数 4.装饰器参数 三.内置函数 salaries={ 'egon':3000, 'alex':10 ...

  9. Python 函数装饰器简明教程

    定义类的静态方法时,就使用了装饰器.其实面向对象中的静态方法都是使用了装饰器. @staticmethod def jump(): print(" 3 meters high") ...

随机推荐

  1. C# 自动生成代码API文档

    暂时没学会正规的转载style临时记录一下 NET中的规范标准注释(一) -- XML注释标签讲解 http://www.cnblogs.com/mq0036/p/3544264.html NET中的 ...

  2. java和h5 canvas德州扑克开发中(二)

    德州扑克网页源码在github上分享 https://github.com/lxr1907/pokers 感兴趣的可以上去看下. 1.通讯使用websocket,主要在message.js中. 2.用 ...

  3. HDU3820 Golden Eggs(最小割)

    题目大概说给一个n*m的格子,每个格子放金蛋或银蛋都会得到不同的价值,当然也可以不放,不过如果存在相邻的两个格子都是金蛋会损失价值g,都是银则损失s.问能得到的最大价值. 有点像二者选一的最小割模型, ...

  4. 【Unity3D自学记录】判断物体是否在镜头内

    判断物体是否在镜头内. 其实很简单的方法 代码如下: using UnityEngine; using System.Collections; public class DJH_IsRendering ...

  5. 【转】C/C++中可变参数函数的实现

    转自:http://www.cnblogs.com/cylee025/archive/2011/05/23/2054792.html 在C语言的stdarg.h头文件中提供了三个函数va_start, ...

  6. C语言获取键盘按键

    在写控制台游戏的时候,发现不管用cin,scanf还是getchar,都不能实时的输入按键,必须要按回车才能读进去,而按回车的话会导致输入异常,所以要使用获取键盘按键的函数. 加入头文件#includ ...

  7. 导出Excel实现 (ASP.NET C# 代码部分)

    背景: 实现导出Excel功能. 技术: ASP.NET  , 采用`Aspose.Cells`第三方组件, C# 实现通用部分. 根据前台Ext Grid完成导入Excel中文列与实际存储列的对应关 ...

  8. 图解 VS2015 如何打包winform 安装程序

    http://learn.flexerasoftware.com/content/IS-EVAL-InstallShield-Limited-Edition-Visual-Studio?lang=10 ...

  9. Flink - ShipStrategyType

      对于DataStream,可以选择如下的Strategy, /** * Sets the partitioning of the {@link DataStream} so that the ou ...

  10. OFS环境,删除Resource 时出现错误失败,应该如何继续

    From the Windows failover cluster manager,select the group listener, stop it, and delete it.  Do the ...