【编程课堂】装饰器浅析

简介: 比如,我们有一个函数 func ,我们希望在不改变函数的前提下记录函数运行的时间。

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/


『码上行动』在线学习班正在开放中,详情回复 码上行动


近期文章推荐阅读:

喏,你们要的 PyCharm 快速上手指南

给伸手党的福利:Python 新手引导

只学2个月编程能写出什么代码?他们表示:You can you code!

如何用100行Python代码做出魔性声控游戏“八分音符酱”

数据分析:当赵雷唱民谣时他唱些什么?

一行代码扫出“敬业福”

我扒了杜蕾斯的微博

Python 爬虫爬取美剧网站

今天,你抢到票了吗?

爆款游戏《贪吃蛇大作战》的 Python 实现

相关文章
|
Java
Java面向对象编程(5)
Java面向对象编程(5)
41 0
|
网络协议 Java 网络安全
Java面向对象编程(43)
Java面向对象编程(43)
58 0
|
人工智能 Java BI
Java面向对象编程(6)
Java面向对象编程(6)
42 0
|
缓存 网络协议 Java
Java面向对象编程(37)
Java面向对象编程(37)
41 0
|
存储 缓存 Java
Java面向对象编程(31)
Java面向对象编程(31)
44 0
|
存储 Java
Java面向对象编程(1)
Java面向对象编程(1)
47 0
|
6月前
|
C++
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
C++从入门到精通:2.1.2函数和类——深入学习面向对象的编程基础
|
6月前
|
Python
Python函数式编程思想与面试实战
【4月更文挑战第14天】Python函数式编程涉及纯函数、高阶函数、匿名函数等概念,能提高代码可读性和并发性。本文探讨其核心理念,分析面试常见问题,如纯函数副作用、高阶函数使用、lambda过度使用等,并提供应对策略。理解并熟练应用函数式编程,对于面试表现和技术提升至关重要。
44 0
|
11月前
|
机器学习/深度学习 缓存 Java
Java面向对象进阶
Java面向对象进阶
|
存储 安全 Java
Java面向对象编程(26)
Java面向对象编程(26)
48 1