简单地理解 Python 的装饰器
关于decorator说的比较透彻,作者是一位很善于讲课的人。
本文系转载,作者:0xFEE1C001
原文链接 www.lightxue.com/understand-python-decorator-the-easy-way
Python有大量强大又贴心的特性,如果要列个最受欢迎排行榜,那么装饰器绝对会在其中。
刚接触装饰器,会觉得代码不多却难以理解。其实装饰器的语法本身挺简单的,复杂是因为同时混杂了其它的概念。下面我们一起抛去无关概念,简单地理解下Python的装饰器。
装饰器的原理
在解释器下跑个装饰器的例子,直观地感受一下。
# make_bold就是装饰器,实现方式这里略去
>>> @make_bold
... def get_content():
... return 'hello world'
...
>>> get_content()
'<b>hello world</b>'
被make_bold装饰的get_content,调用后返回结果会自动被b标签包住。怎么做到的呢,简单4步就能明白了。
1. 函数是对象
我们定义个get_content函数。这时get_content也是个对象,它能做所有对象的操作。
def get_content():
return 'hello world'
它有id,有type,有值。
>>> id(get_content)
140090200473112
>>> type(get_content)
<class 'function'>
>>> get_content
<function get_content at 0x7f694aa2be18>
跟其他对象一样可以被赋值给其它变量。
>>> func_name = get_content
>>> func_name()
'hello world'
它可以当参数传递,也可以当返回值
>>> def foo(bar):
... print(bar())
... return bar
...
>>> func = foo(get_content)
hello world
>>> func()
'hello world'
2. 自定义函数对象
我们可以用class来构造函数对象。有成员函数__call__的就是函数对象了,函数对象被调用时正是调用的__call__。
class FuncObj(object):
def __init__(self, name):
print('Initialize')
self.name= name
def __call__(self):
print('Hi', self.name)
我们来调用看看。可以看到,函数对象的使用分两步:构造和调用(同学们注意了,这是考点)。
>>> fo = FuncObj('python')
Initialize
>>> fo()
Hi python
3. @是个语法糖
装饰器的@没有做什么特别的事,不用它也可以实现一样的功能,只不过需要更多的代码。
@make_bold
def get_content():
return 'hello world'
# 上面的代码等价于下面的
def get_content():
return 'hello world'
get_content = make_bold(get_content)
make_bold是个函数,要求入参是函数对象,返回值是函数对象。@的语法糖其实是省去了上面最后一行代码,使可读性更好。用了装饰器后,每次调用get_content,真正调用的是make_bold返回的函数对象。
4. 用类实现装饰器
入参是函数对象,返回是函数对象,如果第2步里的类的构造函数改成入参是个函数对象,不就正好符合要求吗?我们来试试实现make_bold。
class make_bold(object):
def __init__(self, func):
print('Initialize')
self.func = func
def __call__(self):
print('Call')
return '<b>{}</b>'.format(self.func())
大功告成,看看能不能用。
>>> @make_bold
... def get_content():
... return 'hello world'
...
Initialize
>>> get_content()
Call
'<b>hello world</b>'
成功实现装饰器!是不是很简单?
这里分析一下之前强调的构造和调用两个过程。我们去掉@语法糖好理解一些。
# 构造,使用装饰器时构造函数对象,调用了__init__
>>> get_content = make_bold(get_content)
Initialize
# 调用,实际上直接调用的是make_bold构造出来的函数对象
>>> get_content()
Call
'<b>hello world</b>'
到这里就彻底清楚了,完结撒花,可以关掉网页了~~~(如果只是想知道装饰器原理的话)
函数版装饰器
阅读源码时,经常见到用嵌套函数实现的装饰器,怎么理解?同样仅需4步。
1. def的函数对象初始化
用class实现的函数对象很容易看到什么时候构造的,那def定义的函数对象什么时候构造的呢?
# 这里的全局变量删去了无关的内容
>>> globals()
{}
>>> def func():
... pass
...
>>> globals()
{'func': <function func at 0x10f5baf28>}
不像一些编译型语言,程序在启动时函数已经构造那好了。上面的例子可以看到,执行到def会才构造出一个函数对象,并赋值给变量make_bold。
这段代码和下面的代码效果是很像的。
class NoName(object):
def __call__(self):
pass
func = NoName()
2. 嵌套函数
Python的函数可以嵌套定义。
def outer():
print('Before def:', locals())
def inner():
pass
print('After def:', locals())
return inner
inner是在outer内定义的,所以算outer的局部变量。执行到def inner时函数对象才创建,因此每次调用outer都会创建一个新的inner。下面可以看出,每次返回的inner是不同的。
>>> outer()
Before def: {}
After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa0048>}
<function outer.<locals>.inner at 0x7f0b18fa0048>
>>> outer()
Before def: {}
After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa00d0>}
<function outer.<locals>.inner at 0x7f0b18fa00d0>
3. 闭包
嵌套函数有什么特别之处?因为有闭包。
def outer():
msg = 'hello world'
def inner():
print(msg)
return inner
下面的试验表明,inner可以访问到outer的局部变量msg。
>>> func = outer()
>>> func()
hello world
闭包有2个特点
inner能访问outer及其祖先函数的命名空间内的变量(局部变量,函数参数)。
调用outer已经返回了,但是它的命名空间被返回的inner对象引用,所以还不会被回收。
这部分想深入可以去了解Python的LEGB规则。
4. 用函数实现装饰器
装饰器要求入参是函数对象,返回值是函数对象,嵌套函数完全能胜任。
def make_bold(func):
print('Initialize')
def wrapper():
print('Call')
return '<b>{}</b>'.format(func())
return wrapper
用法跟类实现的装饰器一样。可以去掉@语法糖分析下构造和调用的时机。
>>> @make_bold
... def get_content():
... return 'hello world'
...
Initialize
>>> get_content()
Call
'<b>hello world</b>'
因为返回的wrapper还在引用着,所以存在于make_bold命名空间的func不会消失。make_bold可以装饰多个函数,wrapper不会调用混淆,因为每次调用make_bold,都会有创建新的命名空间和新的wrapper。
到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题)
常见问题
1. 怎么实现带参数的装饰器?
带参数的装饰器,有时会异常的好用。我们看个例子。
>>> @make_header(2)
... def get_content():
... return 'hello world'
...
>>> get_content()
'<h2>hello world</h2>'
怎么做到的呢?其实这跟装饰器语法没什么关系。去掉@语法糖会变得很容易理解。
@make_header(2)
def get_content():
return 'hello world'
# 等价于
def get_content():
return 'hello world'
unnamed_decorator = make_header(2)
get_content = unnamed_decorator(get_content)
上面代码中的unnamed_decorator才是真正的装饰器,make_header是个普通的函数,它的返回值是装饰器。
来看一下实现的代码。
def make_header(level):
print('Create decorator')
# 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level
def decorator(func):
print('Initialize')
def wrapper():
print('Call')
return '<h{0}>{1}</h{0}>'.format(level, func())
return wrapper
# make_header返回装饰器
return decorator
看了实现代码,装饰器的构造和调用的时序已经很清楚了。
>>> @make_header(2)
... def get_content():
... return 'hello world'
...
Create decorator
Initialize
>>> get_content()
Call
'<h2>hello world</h2>'
2. 如何装饰有参数的函数?
为了有条理地理解装饰器,之前例子里的被装饰函数有意设计成无参的。我们来看个例子。
@make_bold
def get_login_tip(name):
return 'Welcome back, {}'.format(name)
最直接的想法是把get_login_tip的参数透传下去。
class make_bold(object):
def __init__(self, func):
self.func = func
def __call__(self, name):
return '<b>{}</b>'.format(self.func(name))
如果被装饰的函数参数是明确固定的,这么写是没有问题的。但是make_bold明显不是这种场景。它既需要装饰没有参数的get_content,又需要装饰有参数的get_login_tip。这时候就需要可变参数了。
class make_bold(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return '<b>{}</b>'.format(self.func(*args, **kwargs))
当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常合适。可变参数不属于装饰器的语法内容,这里就不深入探讨了。
3. 一个函数能否被多个装饰器装饰?
下面这么写合法吗?
@make_italic
@make_bold
def get_content():
return 'hello world'
合法。上面的的代码和下面等价,留意一下装饰的顺序。
def get_content():
return 'hello world'
get_content = make_bold(get_content) # 先装饰离函数定义近的
get_content = make_italic(get_content)
4. functools.wraps有什么用?
Python的装饰器倍感贴心的地方是对调用方透明。调用方完全不知道也不需要知道调用的函数被装饰了。这样我们就能在调用方的代码完全不改动的前提下,给函数patch功能。
为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。伪装得越像,对调用方来说差异越小。有时光伪装函数名和参数是不够的,因为Python的函数对象有一些元信息调用方可能读取了。为了连这些元信息也伪装上,functools.wraps出场了。它能用于把被调用函数的__module__,__name__,__qualname__,__doc__,__annotations__赋值给装饰器返回的函数对象。
import functools
def make_bold(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return '<b>{}</b>'.format(func(*args, **kwargs))
return wrapper
对比一下效果。
>>> @make_bold
... def get_content():
... '''Return page content'''
... return 'hello world'
# 不用functools.wraps的结果
>>> get_content.__name__
'wrapper'
>>> get_content.__doc__
>>>
# 用functools.wraps的结果
>>> get_content.__name__
'get_content'
>>> get_content.__doc__
'Return page content'
实现装饰器时往往不知道调用方会怎么用,所以养成好习惯加上functools.wraps吧。
这次是真•完结了,有疑问请留言,撒花吧~~~
简单地理解 Python 的装饰器相关推荐
- python内置的装饰器最通俗的讲解_如何最简单、通俗地理解Python的装饰器?
目录: 一.笔记 二.我的自学路线 三.笔记目录 一.笔记 1) 装饰器的定义 ① 装饰器就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数. ② 装饰器的功能特点:1. 不修改已有函数的源代 ...
- python装饰器原理wraps(method)(self)_理解Python中装饰器最佳方法~
了解装饰器之前, 可以先了解一下什么是闭包的概念为好: 闭包, 是指在一个函数中定义了一个另外一个函数,内函数里运用了外函数的临时变量(实际参数也是临时变量),并且外函数的返回值是内函数的引用(一切皆 ...
- 如何理解python的装饰器
先来个形象比方 内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也 不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效 ...
- python class用法理解_通过钢铁侠变身快速理解Python的装饰器用法
1 一切都要从函数说起 我们都知道一个函数可以返回一些数据,然后这些数据可以被其他函数调用.函数里还可以有若干个参数,可以让函数根据不同的输入值进行不同的计算,然后得到新的结果. 于是,我们的故事就可 ...
- python中装饰器的作用_如何理解Python装饰器
展开全部 理解Python中的装饰器 @makebold @makeitalic def say(): return "Hello" 打印出如2113下的输出: Hello 你会怎 ...
- python 打印皮卡丘_来简单聊聊python的装饰器呀~
原文链接 来简单聊聊python的装饰器呀~mp.weixin.qq.com 导语 之前很多小伙伴留言给我说看别人写的代码经常会感觉云里雾里的,完全看不懂,其实那些代码无非就是用了些python语法 ...
- python装饰器使用教学,Python教程|简单上手Python中装饰器的使用
如何打造极简主义风格网站教程 .极简主义与其他设计风格<极简主义设计之美> 这篇文章的一部分是讲极简主义是如何与其他设计风格很好配合的.可以把极简主义当作一种属性,而它可以添加或结合其他. ...
- Python的装饰器
详解Python的装饰器 本文源码 https://github.com/tobyqin/python_decorator Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都 ...
- python中装饰器的作用_Python装饰器详解,详细介绍它的应用场景
装饰器的应用场景附加功能 数据的清理或添加:函数参数类型验证 @require_ints 类似请求前拦截数据格式转换 将函数返回字典改为 JSON/YAML 类似响应后篡改为函数提供额外的数据 moc ...
最新文章
- 谷歌又发钱了!给全员发1600美元,包括外包和实习生!还宣布将无限期居家办公!...
- top命令的笔记补充2--如何将top信息后台运行并写入log
- Scrapy Architecture overview--官方文档
- 信息系统项目管理师-项目需求管理知识点
- python数据结构与算法13_python 数据结构与算法 (13)
- 【PAT天梯】【L2-2 小字辈(左子右兄加强版)】(树,水题)
- 分类问题的评价及matrix , precision, recall
- add p4 多个文件_Python实例:对文件夹图片批量添加logo操作
- Advanced Auto Layout
- 常大宿舍路由器安装教程
- single-spa
- 在sagemath中运行python文件
- 一文带你搞懂Vue中的Excel导入导出
- c-lodop自定义分页打印
- Kali Linux系统正确安装指南教程(一)MAC安装kail+Vmware Fusion详细教程(吐血本人测试10次)
- 《Git》版本管理工具的初识与入门
- 计算机无法连接到指定的服务器,无法连接Windows远程服务器的几种解决办法
- 【强化学习论文合集】十二.2018国际人工智能联合会议论文(IJCAI2018)
- HTML——超文本标记语言
- c语言程序设计实践课程,《C语言程序设计》实践课程标准.doc
热门文章
- while用法_when 和 while 的用法区别
- uclinux 嵌入式linux,嵌入式操作系统uCLinux
- 顶岗实习周记java方向_前途虽远,扶摇可接 | 商务日语专业顶岗实习动员大会...
- java 删除list_Java中如何优雅地删除List中的元素
- 六界仙尊h5服务器维护多久,《六界仙尊》5月6日更新维护 公开虚天BOSS坐标
- mysql delete 标记_MySQL删除操作其实是假删除
- git对指定commitid 打tag_Git-命令行-使用 Tag 标记你的代码
- hashmap删除指定key_「集合系列」- 深入浅出分析HashMap
- pandas keyerror: 标签_Hinton新作!越大的自监督模型,半监督学习需要的标签越少...
- 非功能性需求_非接触式喷射自动点胶机的优势在哪儿?