Python装饰器的深入理解
装饰器
1 2 3 4 5 6 7 8 9 10 11 | #装饰器:本质上是函数,(装饰其他函数)就是为其他函数添加附加功能 #原则: 1.不能修改被装饰的函数的源代码 # 2.不能修改被装饰的函数的调用方式 #实现装饰器知识储备 #1.函数即变量 #2.高阶函数 # a.把一个函数名当做实参传给另外一个函数(在不修改被修改函数的源代码的情况下为其添加功能) # b.返回值包含函数名(不修改函数的调用方式) #3.嵌套函数 #小结:高阶函数+嵌套函数=>装饰器! |
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
1 2 3 4 5 6 | >>> def now(): ... print ( '2015-3-25' ) ... >>> f = now >>> f() 2015 - 3 - 25 |
函数对象有一个__name__
属性,可以拿到函数的名字:
1 2 3 4 | >>> now.__name__ 'now' >>> f.__name__ 'now' |
现在,假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
1 2 3 4 5 | def log(func): def wrapper( * args, * * kw): print ( 'call %s():' % func.__name__) return func( * args, * * kw) return wrapper |
观察上面的log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
1 2 3 | @log def now(): print ( '2015-3-25' ) |
把@log
放到now()
函数的定义处,相当于执行了语句:
1 | now = log(now) |
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
1 2 3 4 5 6 7 | def log(text): def decorator(func): def wrapper( * args, * * kw): print ( '%s %s():' % (text, func.__name__)) return func( * args, * * kw) return wrapper return decorator |
这个3层嵌套的decorator用法如下:
1 2 3 4 5 6 7 8 9 10 11 | @log ( 'execute' ) def now(): print #执行结果如下: >>> now() execute now(): 2015 - 3 - 25 #和两层嵌套的decorator相比,3层嵌套的效果是这样的: >>> now = log( 'execute' )(now) |
我们来剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:
1 2 | >>> now.__name__ 'wrapper' |
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
1 2 3 4 5 6 7 8 | import functools def log(func): @functools .wraps(func) def wrapper( * args, * * kw): print ( 'call %s():' % func.__name__) return func( * args, * * kw) return wrapper |
1 2 3 4 5 6 7 8 9 10 11 12 | 或者针对带参数的decorator: import functools def log(text): def decorator(func): @functools .wraps(func) def wrapper( * args, * * kw): print ( '%s %s():' % (text, func.__name__)) return func( * args, * * kw) return wrapper return decorator |
import functools
是导入functools
模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步