分享

【编程课堂】装饰器浅析

 编程教室 2021-03-18

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:///patterns/guide-to-python-function-decorators/

廖雪峰教程:

http://www./wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

如何理解Python装饰器?:

https://www.zhihu.com/question/26930016

12步轻松搞定python装饰器:

http://python./81683/

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多