Python培训
美国上市Python培训机构

400-111-8989

热门课程

谈谈 Python 的函数装饰器

  • 发布: Aurora
  • 来源: 悟空说 by Aurora
  • 时间:2017-12-21 16:41

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。Python 的装饰器有函数装饰器和类装饰器,这篇文章谈谈 Python 的函数装饰器。

函数装饰器的基础知识

函数装饰器用于在代码中「标记」函数,以某种方式增强函数的行为。例如一个名为 decorator 的装饰器:

@decorator

def sayHi():

print('Say hi~')

以上代码与下述写法一样:

def sayHi():

print('Say hi~')

sayHi = decorator(sayHi)

并且这两种写法的效果一样:上述代码执行完毕后所得到的 sayHi 不一定是原来的 sayHi 函数,而是 decorator(sayHi) 返回的函数。

实际上,装饰器只是 Python 的语法糖。前面的例子表明,装饰器可以像常规的可调用对象一样调用,其参数是另一个函数。因此,装饰器的两个特点是:一把被装饰的函数替换成其他的函数,二是装饰器在加载模块时就立即执行。

registry = []

def register(func):

print('running register(%s)' % func)

registry.append(func)

return func

@register

def f1():

print('running f1()')

@register

def f2():

print('running f2()')

def f3():

print('running f3()')

if __name__ == '__main__':

print('running registry -->', registry)

f1()

f2()

f3()

执行上述代码得到的输出如下:

$ python deco.py

running register(<function f1 at 0x1039afcf8>)

running register(<function f2 at 0x1039b5398>)

('running registry -->', [<function f1 at 0x1039afcf8>, <function f2 at 0x1039b5398>])

running f1()

running f2()

running f3()

可以看到 deco.py 在执行 main 函数之前,运行了 f1() 函数和 f2() 函数。继续验证,导入 deco.py,输出如下:

>>> import deco

running register(<function f1 at 0x10392dc8b>)

running register(<function f2 at 0x10392de2a>)

查看 registry 的值:

>>> deco.registry

[<function f1 at 0x10392dc8b>), <function f2 at 0x10392de2a>]

看到这里你应该能明白了,函数装饰器在导入模块时是立即执行的,而被装饰的函数只在明确调用时运行。

接下来举例函数装饰器的应用。

函数装饰器的应用

现在需求是将原来代码中的 sayHi 函数执行之前输出当前的时间。

import datetimedef print_now(func):

def decorate():

now = datetime.datetime.now()

func_name = func.__name__

print('<%s (%s)>' % (str(now), func_name))

func()

return decorate

@print_now

def sayHi():

print('say Hi~')

然后调用 sayHi 函数,就能看到每次输出“say Hi~”之前的装饰器里定义的输出了。这种方式避免了改动原来的代码,对于不熟悉原来的代码逻辑的程序员来说是一种很好的修改办法。然而,装饰器也有缺陷,它替换了被装饰的函数,也遮盖了被装饰的函数的 __name__ 、 __doc__ 和 __modual__ 属性。

我们接着查看 sayHi 函数的 __name__ 属性:

>>> sayHi.__name__

'decorate'

为什么加了 print_now 这个装饰器的 sayHi 函数的 __name__ 属性从“sayHi”变成了“decorate”?前面说了,装饰器遮盖了被装饰的函数的 __name__ 、 __doc__ 和 __modual__ 属性。通过使用 Python 内置的 functools.wraps 装饰器可以把相关属性从 sayHi 函数复制到 print_now 装饰器里。

import functoolsimport datetime

def print_now(func): @functools.wraps(func)

def decorate():

now = datetime.datetime.now()

func_name = func.__name__

print('<%s (%s)>' % (str(now), func_name))

func()

return decorate

@print_now

def sayHi():

print('say Hi~')

执行上述代码:

>>> sayHi()

2017-12-20 21:25:18.045965 (sayHi)

say Hi~

>>> sayHi.__name__

'sayHi'

可以看到 sayHi 函数的 __name__ 属性恢复正常。

那么,如果被装饰的函数带参数,该如何定义装饰器呢?将 sayHi 函数加上一个 name 参数:

def sayHi(name):

print('say Hi~', name)

那么 print_now 装饰器的定义如下:

import functools

import datetime

def print_now(func):

@functools.wraps(func)

def decorate(arg):

now = datetime.datetime.now()

func_name = func.__name__

print('<%s (%s)>' % (str(now), func_name)) func(arg)

return decorate

@print_now

def sayHi(name):

print('say Hi~ %s' % name)

上述代码只是在 print_now 装饰器里的 decorate 函数传递了 sayHi 函数的参数,执行上述代码:

>>> sayHi('Eva')

2017-12-20 21:46:48.521607 (sayHi)

say Hi~ Eva

重叠装饰器

重叠装饰器就是对被装饰的函数同时使用多个装饰器。

例如,有装饰器 @d1、@d2、@d3,按顺序把这三个装饰器应用于 func 函数:

@d1

@d2

@d3

def func():

pass

在上面的代码中,@d2 应用于 @d3 返回的函数上,@d1 应用于 @d2 返回的函数上。

上述代码于下述代码的效果一样:

def func():

pass

func = d1(d2(d3(func)))

参数化装饰器

参数化装饰器就是装饰器函数也带参数,如 @print_now(to_prt=True)。现在,给 print_now 装饰器加上 to_prt 参数,当 to_prt 参数值为 True 时,在 sayHi 函数执行之前输出当前的时间;当 to_prt 参数值为 False 时,则不输出当前的时间。

import functools

import datetime

def print_now(to_prt=True): def to_prt_decorator(func):

@functools.wraps(func)

def decorate(arg): if to_prt:

now = datetime.datetime.now()

func_name = func.__name__

print('<%s (%s)>' % (str(now), func_name))

func(arg)

return decorate return to_prt_decorator @print_now(to_prt=False)

def sayHi(name):

print('say Hi~ %s' % name)

上述代码中的 print_now 装饰器又增加了一层嵌套,并且使用装饰器的 to_prt 参数在 sayHi 函数执行之前来判断是否输出当前的时间。执行上述代码:

>>> sayHi('Eva')

say Hi~ Eva

可以看到没有打印当前的时间了。

实际上,Python 是把被装饰的函数作为第一个参数传给装饰器函数来实现参数化装饰器的。如果不使用装饰器的 @ 语法,则调用 sayHi 函数的方法如下:

sayHi = print_now(to_prt=True)(sayHi)

>>> sayHi('Eva')

<2017-12-20 22:16:39.494790 (sayHi)>

say Hi~ Eva

本文内容转载自网络,来源/作者信息已在文章顶部表明,版权归原作者所有,如有侵权请联系我们进行删除!

预约申请免费试听课

上一篇:看了被人工智能取代的职业,我决定...学习Python!
下一篇:走进小学教材,Python何德何能?9图对比道出真相

Python培训线上和线下的区别

不懂pandas,你怎么Python语言进行机器编程​?

了解Python语言的2D绘图库​Matplotlib,才能绘制出专业图像!

Python培训干货分享|不可不知的Python 爬虫工具

选择城市和中心
贵州省

广西省

海南省