【编程课堂】装饰器浅析
Python 拥有丰富强大的功能和表达特性,其中之一便是装饰器,装饰器能够在不改变函数、方法、类本身的情况下丰富他们的功能。
比如,我们有一个函数 func
,我们希望在不改变函数的前提下记录函数运行的时间。
再比如,web 开发中,对于某一功能 vip_func
,只允许 VIP 用户使用,在不改变该函数本身的情况下,该如何做呢?
类似的例子还有很多,今天我们结合大量的例子来谈谈装饰器。对装饰器不太了解的同学,准备空闲的 30 分钟,打开编辑器,一起开始本周的学习之旅吧!
1、关于函数你应该知道
在正式介绍装饰器之前,很有必要了解一些函数的基本特性,这对理解装饰器很有帮助。
1.1 函数可以作为变量
def print_func(name): return 'hello,'+ name
func = print_func print(func('world'))
#结果 : hello,world
从以上的例子可以看到,函数可以作为变量传递。
1.2 将函数传递给函数
既然函数可以作为变量,那就可以传递给另一个函数。
def prt_fun(): return 'hello,world'
def call_func(func): return func() print(call_func(prt_fun))
#结果 : hello,world
1.3 函数嵌套函数
先看一个简单的无参数的函数嵌套例子
def func_wrap(): def prt_func(): return 'hello,world' return prt_func hlowld = func_wrap() print(hlowld())
#结果 : hello,world
再来看一个将普通字符串作为参数的函数嵌套例子
def func_wrap(): def prt_func(name): return 'hello,'+name
return prt_func hlo = func_wrap() print(hlo('crossin'))
#结果 : hello,crossin
最后,我们再来看将一个函数作为参数的函数嵌套例子,该嵌套函数的作用是在经过某函数处理的字符串两边添加 <p>...</p>
标签。
# 首先定义一个普通的函数
def print_text(name): return 'hello,'+ name
# 再定义一个嵌套函数,分别以函数和普通的字符串作为参数
def add_tag(func): def prt_func(name): return '<p>{0}</p>'.format(func(name))
return prt_func
# 将函数作为参数传递给 add_tag
hlo = add_tag(print_text)
# 将 'crossin' 作为参数传递给 hlo
print(hlo('crossin'))
# 结果 : <p>hello,crossin</p>
到这里,可能有些同学会有点懵了,没关系,请结合上一个例子和 1.2 节内容再理解一下,同时自己动手实现一个类似的函数。
没问题的同学接着往下看。
2、装饰器
本节正式进入装饰器的知识,装饰器的核心内容其实就是将函数作为参数传递给另一个函数。
装饰器的使用比较简单,如下图中的伪代码所示,decorator
为装饰器函数,func
为被处理函数。
@decrator
def func(): pass
2.1 无参数的装饰器
首先回到 1.3 节, 我们将此代码片段稍作修改,就是一个标准的装饰器实例
# 定义一个嵌套函数,分别以函数和普通的字符串作为参数
def add_tag(func): def prt_func(name): return '<p>{0}</p>'.format(func(name))
return prt_func
# 定义一个普通的函数,并调用装饰器
@add_tag
def print_text(name): return 'hello,'+ name print(print_text('crossin'))
# 结果 : <p>hello,crossin</p>
是不是很神奇,仅仅调用一句 @add_tag
就轻松的将 hello,crossin
包裹了起来,实现的原理见 1.3 节解析,简单来讲就是将函数和字符串都作为参数传递给装饰器函数。
至此,你可以开开心心的将该装饰器使用在别的函数身上。
@add_tag
def func1(word): return 'arg is '+ word print(func1('abc'))
# 结果 : <p>arg is abc</p>
2.2 带参数的装饰器
通过 2.1 节内容,我们对装饰器有了简单的理解,问题也随之而来,刚刚我们只能使用 <p>
标签包裹,接下来,我们看看如何在不重新写其他装饰器的前提下,随心所欲的使用 <div>
、<img>
等标签包裹 文本。
# 定义装饰器函数
def add_tag(tagname): def decorator(func): def prt_func(name): return '<{0}>{1}</{0}>'.format(tagname,func(name))
return prt_func
return decorator
@add_tag('div')
def print_text(name): return 'hello,'+name print(print_text('crossin'))
# 结果 : <div>arg is abc</div>
这里,把原装饰器函数改为了 3 层嵌套,形式上虽然复杂了些,但原理上与之前的函数相同,实际运行中分别传入 div
字符串,print_text
函数地址, crossin
字符串,共同作用之后得到最终的结果。
2.3 __name__ 之惑
__name__
可以获得函数、方法、类名,比如我们定义一个函数,然后获取其函数名
def func(): pass
print(func.__name__)
# 结果 : func
但是,当我们去获取刚刚使用了装饰器的函数 print_text
的 __name__
时
print(print_text.__name__)
# 结果 : prt_func
奇怪,为什么这里变为了装饰器内的函数名 prt_func
,而不是 print_text
,这是因为在装饰器中,prt_func
覆写了 print_text
函数的 __name__
、__doc__
、__modual__
三个属性。
改回来也相当简单,使用Python 中的 functools.wraps
装饰器就可以了。
from functools import wrap
# 定义装饰器函数
def add_tag(tagname): def decorator(func): @wraps(func) def prt_func(name): return '<{0}>{1}</{0}>'.format(tagname,func(name))
return prt_func
return decorator print(print_text.__name__)
# 结果 : print_text
3、小结
说了这么多,相信大家都看累了,来动动手吧。定义一个函数,添加一个装饰器输出该函数的运行时间。
同时,提供一些参考资料:
A guide to Python’s function decorators:
http://thecodeship.com/patterns/guide-to-python-function-decorators/
廖雪峰教程:
http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
如何理解Python装饰器?:
https://www.zhihu.com/question/26930016
12步轻松搞定python装饰器:
http://python.jobbole.com/81683/