写在前面:
- 1、Python里面一切皆对象,记住这点,很多东西会好理解许多。
- 2、因为我记不住英语单词,而且特别讨厌起名字,本文中变量名大部分都是中文命名,可能会引起些许不适。
- 3、文章是jupyter notebook里写好,运行过,然后导入过来的。
装饰器
我们先看一个普通函数
def 一个普通函数():
"""
大家好,
我是普通函数的文档。
"""
print("我是一个普通函数。")
return "一个普通函数。"
执行一下:
一个普通函数()
我是一个普通函数。
'一个普通函数。'
来看一下他的描述文档
一个普通函数.__doc__
'\n 大家好,\n 我是普通函数的文档。\n '
现在,我们来定义一个装饰器函数。
def 一个装饰器(要装饰的函数):
"""
大家好,
我是装饰器的文档。
"""
def 装饰起来():
"""
大家好,
我是装饰器里 装饰起来 这部分的文档。
"""
print("开始装饰了哟!下面执行普通函数:\n")
return 要装饰的函数() #执行要装饰的函数,并返回其结果。此处返回的结果!!!
return 装饰起来 #返回 装饰起来 这个函数。此处返回的是函数!!!
一个新函数=一个装饰器(一个普通函数)
一个新函数()
开始装饰了哟!下面执行普通函数:
我是一个普通函数。
'一个普通函数。'
# 用装饰器重新定义函数
@一个装饰器
def 一个普通函数():
"""
大家好,
我是普通函数的文档。
"""
print("我是一个普通函数。")
return "一个普通函数。"
一个普通函数()
开始装饰了哟!下面执行普通函数:
我是一个普通函数。
'一个普通函数。'
语法糖
在上面的例子里可以看到,加了@一个装饰器 以后想当于执行了 一个装饰器(一个普通函数) 。返回结果是一个新的,名字还叫"一个普通函数" 的 函数。这种用法叫语法糖。(后记:??貌似百度百科里语法糖不是这么定义的。)
我们再看一个例子:
def 语法糖测试():
return "我运行了。"
语法糖测试()
'我运行了。'
执行结果=print(语法糖测试)
<function 语法糖测试 at 0x7f31303ecc80>
type(执行结果)
NoneType
Python 函数里如果没有return语句,返回结果为None。return None可以简写为return。
这里我们可以看见print函数返回结果是None。
让我们试试语法糖:@print
@print
def 语法糖测试():
return "我运行了。"
<function 语法糖测试 at 0x7f31303ecf28>
## 试试这个函数有没有被定义成功
语法糖测试()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-47-0a2f581b1252> in <module>
1 ## 试试这个函数有没有被定义成功
----> 2 语法糖测试()
TypeError: 'NoneType' object is not callable
type(语法糖测试)
NoneType
语法糖测试 并没有被成功定义成函数,而NoneType。因为用了__@print__,相当于执行了 语法糖测试=print(语法糖测试), 对象彻底被改变了。
总结: 语法糖的用法就是 先__@a__ 函数,再定义一个新函数 b ;结果就相当于先定函数 b ,然后执行 b=a(b)
再试一个 @callable 的例子
##试试callable函数
@callable
def 语法糖测试():
return "我运行了。"
语法糖测试
True
type(语法糖测试)
bool
结果就是 语法糖测试=callable(语法糖测试) 了,返回结果是bool型的Ture,跟预测一样。
回到装饰器的问题
前面定义好了一个带装饰器的函数 一个普通函数 ,现在看看他的属性有没有因为前面的“装饰器”发生变化
一个普通函数.__doc__
'\n 大家好,\n 我是装饰器里 装饰起来 这部分的文档。\n '
上面的文档变成了装饰器里面的文档。实际上这种改变了函数属性的函数不能叫做装饰器。
真正的装饰器应该满足几个条件:
- 增加新的功能
- 不能改变原函数的属性
- 不能改变原函数的返回结果
- 不能改变原函数的调用方式
来重新定义一个装饰器。这里需要functools 模块里的的wraps装饰器。
from functools import wraps##要想保留属性,需要的就是Python自己带的wraps装饰器
def 一个真正的装饰器(要装饰的函数):
"""
一个真正的装饰器的文档
"""
@wraps(要装饰的函数)
def 装饰起来(*很多非关键字参数,**很多关键字参数): ##真正的装饰器自然要考虑参数的问题,管你传入多少参数,统统打包。不过一个函数要是不按先非关键字参数后关键字参数的方式传参就麻烦了。好在,Python已经严格规定了如果第一个参数使用了关键字绑定,后面的参数也必须使用关键字绑定!
"""
一个真正的装饰器,装饰起来 部分的文档
"""
print("我是真正的装饰器,开始装饰了哟!下面执行普通函数:\n")
return 要装饰的函数(*很多非关键字参数,**很多关键字参数) #执行要装饰的函数,并返回其结果。此处返回的是函数执行结果!!!
return 装饰起来 #返回 装饰起来 这个函数,并不执行。此处返回的是函数!!!
@一个真正的装饰器
def 另一个普通函数(*关键字参数,**非关键字参数):# Python已经严格规定了如果一个参数使用了关键字参数,其后面的参数也必须使用关键字参数!!
"""
大家好,
我是另一个普通函数的文档。
"""
print("我是另一个普通函数,我要返回传入的参数")
return 关键字参数,非关键字参数
另一个普通函数("我是参数","我不是参数",一个参数="我是不是参数",参数="我是谁")
我是真正的装饰器,开始装饰了哟!下面执行普通函数:
我是另一个普通函数,我要返回传入的参数
(('我是参数', '我不是参数'), {'一个参数': '我是不是参数', '参数': '我是谁'})
另一个普通函数.__doc__
'\n 大家好,\n 我是另一个普通函数的文档。\n '
属性并没有改变!这才是真的装饰器!
看看另一种自己带参数的装饰器
def 装饰器第一层(装饰器自己的参数):
def 装饰器第二层(要装饰的函数):
@wraps(要装饰的函数)
def 装饰器第三层(*args, **kwargs):
print('执行 %s():' % 要装饰的函数.__name__)
print('参数 = {}'.format(*args))
print('装饰器自己的参数 = {}'.format(装饰器自己的参数))
return 要装饰的函数(*args, **kwargs)
return 装饰器第三层
return 装饰器第二层
@装饰器第一层##此处没有给装饰器参数
def 另一个普通函数(*关键字参数,**非关键字参数):# Python已经严格规定了如果一个参数使用了关键字参数,其后面的参数也必须使用关键字参数!!
"""
大家好,
我是另一个普通函数的文档。
"""
print("我是另一个普通函数,我要返回传入的参数")
return 关键字参数,非关键字参数
另一个普通函数("hh")
<function __main__.装饰器第一层.<locals>.装饰器第二层.<locals>.装饰器第三层>
此时很清晰的可以看出,因为,我们没有给 装饰器第一层 指定参数,传入 装饰器第一层 的参数依然是 另一个普通函数 这个函数。最终返回了 装饰器第三层 这个函数,而没有执行 要装饰的函数。
@装饰器第一层("你好")##此处给了装饰器参数
def 另一个普通函数(*关键字参数,**非关键字参数):
"""
大家好,
我是另一个普通函数的文档。
"""
print("我是另一个普通函数,我要返回传入的参数")
return 关键字参数,非关键字参数
另一个普通函数("大家好","我很好")
执行 另一个普通函数():
参数 = 大家好
装饰器自己的参数 = 你好
我是另一个普通函数,我要返回传入的参数
(('大家好', '我很好'), {})
给了 装饰器第一层 参数后,传入 装饰器第一层 的参数就是指定的参数了。而 要装饰的函数 向下传给了__装饰器第二层__。这相当于执行了:
另一个普通函数=装饰器第一层("你好")(另一个普通函数)
此处跟前面说的@的用法是一致的。@带上参数的函数,如:
@a(b)
def c
@a(b)后,a(b)被视为整体(一个独立的对象)。此处的装饰器是__a(b),而不是__a。就是相当于先__def c,然后执行__c=a(b)©。当然__a(b)__必须是函数(意味着__a__的返回值必须是函数),否则会因a(b)无法被调用而报错。
补充:
Python中在对象后面加()就意外着调用该对象,该对象必须有callable(_call_)属性才行。