先混个眼熟

谁可以作为装饰器(可以将谁编写成装饰器):

函数

方法

实现了__call__的可调用类

装饰器可以去装饰谁(谁可以被装饰):

函数

方法

基础:函数装饰器的表现方式

假如你已经定义了一个函数funcA(),在准备定义函数funcB()的时候,如果写成下面的格式:

@funcA

def funcB():...

表示用函数funcA()装饰函数funcB()。当然,也可以认为是funcA包装函数funcB。它等价于:

def funcB():...

funcB = funcA(funcB)

也就是说,将函数funcB作为函数funcA的参数,funcA会重新返回另一个可调用的对象(比如函数)并赋值给funcB。

所以,funcA要想作为函数装饰器,需要接收函数作为参数,并且返回另一个可调用对象(如函数)。例如:

def funcA(F):

...

...

return Callable

注意,函数装饰器返回的可调用对象并不一定是原始的函数F,可以是任意其它可调用对象,比如另一个函数。但最终,这个返回的可调用对象都会被赋值给被装饰的函数变量(上例中的funcB)。

函数可以同时被多个装饰器装饰,后面的装饰器以前面的装饰器处理结果为基础进行处理:

@decorator1

@decorator2

def func():...

# 等价于

func = decorator1(decorator2(func))

当调用被装饰后的funcB时,将自动将funcB进行装饰,并调用装饰后的对象。所以,下面是等价的调用方式:

funcB() # 调用装饰后的funcB

funcA(funcB)()

了解完函数装饰器的表现后,大概也能猜到了,装饰器函数可以用来扩展、增强另外一个函数。实际上,内置函数中staticmethod()、classmethod()和property()都是装饰器函数,可以用来装饰其它函数,在后面会学到它们的用法。

两个简单的例子

例如,函数f()返回一些字符串,现在要将它的返回结果转换为大写字母。可以定义一个函数装饰器来增强函数f()。

def toupper(func):

def wrapper(*args, **kwargs):

result = func(*args, **kwargs)

return result.upper()

return wrapper

@toupper

def f(x: str): # 等价于f = toupper(f)

return x

res = f("abcd")

print(res)

上面toupper()装饰f()后,调用f("abcd")的时候,等价于执行toupper(f)("abcd"),参数"abcd"传递给装饰器中的wrapper()中的*args,在wrapper中又执行了f("abcd"),使得原本属于f()的整个过程都完整了,最后返回result.upper(),这部分是对函数f()的扩展部分。

注意,上面的封装函数wrapper()中使用了*args **kwargs,是为了确保任意参数的函数都能正确执行下去。

再比如要计算一个函数autodown()的执行时长,可以额外定义一个函数装饰器timecount()。

import time

# 函数装饰器

def timecount(func):

def wrapper(*args, **kwargs):

start = time.time()

result = func(*args, **kwargs)

end = time.time()

print(func.__name__, end - start)

return result

return wrapper

# 装饰函数

@timecount

def autodown(n: int):

while n > 0:

n -= 1

# 调用被装饰的函数

autodown(100000)

autodown(1000000)

autodown(10000000)

执行结果:

autodown 0.004986763000488281

autodown 0.05684685707092285

autodown 0.5336081981658936

上面wrapper()中的return是多余的,是因为这里装饰的autodown()函数自身没有返回值。但却不应该省略这个return,因为timecount()可以去装饰其它可能有返回值的函数。

@functools.wraps

前面的装饰器代码逻辑上没有什么问题,但是却存在隐藏的问题:函数的元数据信息丢了。比如doc、注解等。

比如下面的代码:

@timecount

def autodown(n: int):

''' some docs '''

while n > 0:

n -= 1

print(autodown.__name__)

print(autodown.__doc__)

print(autodown.__annotations__)

执行结果为:

wrapper

None

{}

所以,必须要将被装饰函数的元数据保留下来。可以使用functools模块中的wraps()装饰一下装饰器中的wrapper()函数。如下:

import time

from functools import wraps

def timecount(func):

@wraps(func)

def wrapper(*args, **kwargs):

start = time.time()

result = func(*args, **kwargs)

end = time.time()

print(func.__name__, end - start)

return result

return wrapper

现在,再去查看autodown函数的元数据信息,将会得到被保留下来的内容:

autodown

some doc

{'n': }

所以,wraps()的简单用法是:向wraps()中传递的func参数,那么func的元数据就会被保留下来。

上面@wraps(func)装饰wrapper的过程等价于:

def wrapper(*args, **kwargs):...

wrapper = wraps(func)(wrapper)

请注意这一点,因为在将类作为装饰器的时候,经常会在__init__(self, func)里这样使用:

class cls:

def __init__(self, func):

wraps(func)(self)

...

def __call__(self, *args, **kwargs):

...

解除装饰

函数被装饰后,如何再去访问未被装饰状态下的这个函数?@wraps还有一个重要的特性,可以通过被装饰对象的__wrapped__属性来直接访问被装饰对象。例如:

autodown.__wrapped__(1000000)

new_autodown = autodown.__wrapped__

new_autodown(1000000)

上面的调用不会去调用装饰后的函数,所以不会输出执行时长。

注意,如果函数被多个装饰器装饰,那么通过__wrapped__,将只会解除第一个装饰过程。例如:

@decorator1

@decorator2

@decorator3

def f():...

当访问f.__wrapped__()的时候,只有decorator1被解除,剩余的所有装饰器仍然有效。注意,python 3.3之前是略过所有装饰器。

下面是一个多装饰的示例:

from functools import wraps

def decorator1(func):

@wraps(func)

def wrapper(*args, **kwargs):

print("in decorator1")

return func(*args, **kwargs)

return wrapper

def decorator2(func):

@wraps(func)

def wrapper(*args, **kwargs):

print("in decorator2")

return func(*args, **kwargs)

return wrapper

def decorator3(func):

@wraps(func)

def wrapper(*args, **kwargs):

print("in decorator3")

return func(*args, **kwargs)

return wrapper

@decorator1

@decorator2

@decorator3

def addNum(x, y):

return x+y

返回结果:

in decorator1

in decorator2

in decorator3

5

in decorator2

in decorator3

5

如果不使用functools的@wraps的__wrapped__,想要手动去引用原始函数,需要做的工作可能会非常多。所以,如有需要,直接使用__wrapped__去调用未被装饰的函数比较好。

另外,并不是所有装饰器中都使用了@wraps。

带参数的函数装饰器

函数装饰器也是可以带上参数的。

@decorator(x,y,z)

def func():...

它等价于:

func = decorator(x,y,z)(func)

它并不是"天生"就这样等价的,而是根据编码规范编写装饰器的时候,通常会这样。其实带参数的函数装饰器写起来有点绕:先定义一个带有参数的外层函数,它是外在的函数装饰器,这个函数内包含了真正的装饰器函数,而这个内部的函数装饰器的内部又包含了被装饰的函数封装。也就是函数嵌套了一次又一次。

所以,结构大概是这样的:

def out_decorator(some_args):

...SOME CODE...

def real_decorator(func):

...SOME CODE...

def wrapper(*args, **kwargs):

...SOME CODE WITH func...

return wrapper

return real_decorator

# 等价于func = out_decorator(some_args)(func)

@out_decorator(some_args)

def func():...

下面是一个简单的例子:

from functools import wraps

def out_decorator(x, y, z):

def decorator(func):

@wraps(func)

def wrapper(*args, **kwargs):

print(x)

print(y)

print(z)

return func(*args, **kwargs)

return wrapper

return decorator

@out_decorator("xx", "yy", "zz")

def addNum(x, y):

return x+y

print(addNum(2, 3))

参数随意的装饰器

根据前面介绍的两种情况,装饰器可以带参数、不带参数,所以有两种装饰的方式,要么是下面的(1),要么是下面的(2)。

@decorator # (1)

@decorator(x,y,z) # (2)

所以,根据不同的装饰方式,需要编写是否带参数的不同装饰器。

但是现在想要编写一个将上面两种参数方式统一起来的装饰器。

可能第一想法是让装饰器参数默认化:

def out_decorator(arg1=X, arg2=Y...):

def decorator(func):

@wraps(func)

def wrapper(*args, **kwargs):

...

return wrapper

return decorator

现在可以用下面两种方式来装饰:

@out_decorator()

@out_decorator(arg1,arg2)

虽然上面两种装饰方式会正确进行,但这并非合理做法,因为下面这种最通用的装饰方式会错误:

@out_decorator

为了解决这个问题,回顾下前面装饰器是如何等价的:

# 等价于 func = decorator(func)

@decorator

def func():...

# 等价于 func = out_decorator(x, y, z)(func)

@out_decorator(x, y, z)

def func():...

上面第二种方式中,out_decorator(x,y,z)才是真正返回的内部装饰器。所以,可以修改下装饰器的编写方式,将func也作为out_decorator()的其中一个参数:

from functools import wraps,partial

def decorator(func=None, arg1=X, arg2=Y):

# 如果func为None,说明触发的带参装饰器

# 直接返回partial()封装后的装饰器函数

if func is None:

decorator_new = partial(decorator, arg1=arg1, arg2=arg2)

return decorator_new

#return partial(decorator, arg1=arg1, arg2=arg2)

# 下面是装饰器的完整装饰内容

@wraps(func)

def wrapper(*args, **kwargs):

...

return wrapper

上面使用了functools模块中的partial()函数,它可以返回一个新的将某些参数"冻结"后的函数,使得新的函数无需指定这些已被"冻结"的参数,从而减少参数的数量。如果不知道这个函数,参考partial()用法说明。

现在,可以统一下面3种装饰方式:

@decorator()

@decorator(arg1=x,arg2=y)

@decorator

前两种装饰方式,等价的调用方式是decorator()(func)和decorator(arg1=x,arg2=y)(func),它们的func都为None,所以都会通过partial()返回通常的装饰方式@decorator所等价的形式。

需要注意的是,因为上面的参数结构中包含了func=None作为第一个参数,所以带参数装饰时,必须使用keyword格式来传递参数,不能使用位置参数。

下面是一个简单的示例:

from functools import wraps, partial

def decorator(func=None, x=1, y=2, z=3):

if func is None:

return partial(decorator, x=x, y=y, z=z)

@wraps(func)

def wrapper(*args, **kwargs):

print("x: ", x)

print("y: ", y)

print("z: ", z)

return func(*args, **kwargs)

return wrapper

下面3种装饰方式都可以:

@decorator

def addNum(a, b):

return a + b

print(addNum(2, 3))

print("=" * 40)

@decorator()

def addNum(a, b):

return a + b

print(addNum(2, 3))

print("=" * 40)

# 必须使用关键字参数进行装饰

@decorator(x="xx", y="yy", z="zz")

def addNum(a, b):

return a + b

print(addNum(2, 3))

返回结果:

x: 1

y: 2

z: 3

5

====================

x: 1

y: 2

z: 3

5

====================

x: xx

y: yy

z: zz

5

python装饰器函数-python装饰器1:函数装饰器详解相关推荐

  1. python类装饰器详解-Python类中的装饰器在当前类中的声明与调用详解

    我的Python环境:3.7 在Python类里声明一个装饰器,并在这个类里调用这个装饰器. 代码如下: class Test(): xx = False def __init__(self): pa ...

  2. python3d动态图-Python图像处理之gif动态图的解析与合成操作详解

    本文实例讲述了Python图像处理之gif动态图的解析与合成操作.分享给大家供大家参考,具体如下: gif动态图是在现在已经司空见惯,朋友圈里也经常是一言不合就斗图.这里,就介绍下如何使用python ...

  3. python画椭圆-python opencv圆、椭圆与任意多边形的绘制实例详解

    圆形的绘制 : OpenCV中使用circle(img,center,radius,color,thickness=None,lineType=None,shift=None)函数来绘制圆形 impo ...

  4. 【Python】基金/股票 最大回撤率计算与绘图详解(附源码和数据)

    如果你想找的是求最大回撤的算法,请跳转:[Python] 使用动态规划求解最大回撤详解 [Python]基金/股票 最大回撤率计算与绘图详解(附源码和数据) 0. 起因 1. 大成沪深300指数A 5 ...

  5. [Python图像处理] 三十三.图像各种特效处理及原理万字详解(毛玻璃、浮雕、素描、怀旧、流年、滤镜等)...

    此文转载自:https://blog.csdn.net/Eastmount/article/details/111568397#commentBox 该系列文章是讲解Python OpenCV图像处理 ...

  6. Python零基础速成班-第14讲-Python处理Excel和Word,使用openpyxl和docx包详解,图表入门

    Python零基础速成班-第14讲-Python处理Excel和Word,使用openpyxl和docx包详解,图表入门 学习目标 Python处理Excel(使用openpyxl包).图表入门\ P ...

  7. python爬取图片-Python爬取网页中的图片(搜狗图片)详解

    前言 最近几天,研究了一下一直很好奇的爬虫算法.这里写一下最近几天的点点心得.下面进入正文: 你可能需要的工作环境: Python 3.6官网下载 本地下载 我们这里以sogou作为爬取的对象. 首先 ...

  8. python硬件交互_对Python的交互模式和直接运行.py文件的区别详解

    对Python的交互模式和直接运行.py文件的区别详解 看到类似C:\>是在Windows提供的命令行模式,看到>>>是在Python交互式环境下. 在命令行模式下,可以执行p ...

  9. python编译器怎么运行不在路径中的py文件_对python当中不在本路径的py文件的引用详解...

    众所周知,如果py文件不在当前路径,那么就不能import,因此,本文介绍如下两种有效的方法: 方法1: 修改环境变量,在~/.bashrc里面进行修改,然后source ~/.bashrc 方法2: ...

  10. python类继承中构造方法_第8.3节 Python类的__init__方法深入剖析:构造方法与继承详解...

    第8.3节Python类的__init__方法深入剖析:构造方法与继承详解 一.    引言 上两节介绍了构造方法的语法及参数,说明了构造方法是Python的类创建实例后首先执行的方法,并说明如果类没 ...

最新文章

  1. WordPress主题制作函数
  2. Python 技术篇 - 使用unicode_escape对js的escape()方法编码后的字符串进行解码实例演示
  3. python笔记: staticmethod classmethod
  4. C++类中成员变量的初始化有两种方式
  5. CentOS yum安装mcrypt详细图解教程[linux]
  6. 2019年IT界,程序员是否不好找工作了?
  7. 漫画:如何给女朋友解释什么是“锟斤拷”?
  8. java oop6_JavaOOP_03 构造方法
  9. 浪潮之巅阅读笔记02
  10. Invest模型 ——生境质量计算
  11. linux tracker服务器搭建,linux 下 BT Tracker服务器搭建
  12. Python pandas.DataFrame.median函数方法的使用
  13. 微信Log日志分析——初步探索
  14. Java实现支付功能代码
  15. 五款好用到爆炸的小众软件,用过的都好说!建议收藏转发
  16. 对现有计算机应用的建议,对计算机课程的建议
  17. 使用公众号快速申请小程序的流程
  18. 植树节汇报之后的突发奇想
  19. input框前追加图片
  20. Myeclipse反向工程后造成的SQL syntax

热门文章

  1. 搭建android开发环境,android studio + Genymotion
  2. [原创] Matlab 指派问题模型代码
  3. 算法笔记(一)——简述时间、空间复杂度分析
  4. 8086 汇编指令手册查询(转)
  5. 如何成为一名专家级的开发人员
  6. (转)动态SQL和PL/SQL的EXECUTE IMMEDIATE选项
  7. 0-1背包 java_0-1背包问题,java的动态规划如题,代码如下public
  8. python是高级动态编程语言-python是一种跨平台、开源、免费的高级动态编程语言,对么...
  9. 未来教育python视频百度云-青橙课程 | 人工智能走进课堂,为未来教育高质量发展赋能!...
  10. 财务大数据比赛有python吗-Python大数据与机器学习之NumPy初体验