目录
测试环境:
操作系统: Window 10
工具:Pycharm
Python: 3.7
补充说明:函数也是对象
对象其实就是一个属性的集合,函数里面各种代表了某种属性的变量或参数等等,类里面代表了某种函数对象或者某种属性的变量,函数对象里面可能有代表某种属性的变量,而其中又可能有函数对象,而函数对象里面可能又有代表某种属性的变量,而且又有某种函数对象…
这样子想下去,大概都要乱了。这里对象是应用到了各种编程语言 —— Java、C++、Python、JavaScript 等等
一切皆对象 —— 类、函数
反正只要记住一点就可以了,对象指的是内部存在可以代表某种属性的东西 —— 变量 (空变量) / 函数 (空函数)
decorator
—— 装饰器
wrapper
—— 包装器、封装器
一、无参数的装饰器
如何理解函数作为Python 装饰器的参数
理解 Python 装饰器,首先要先知道 Python 的函数可以嵌套(内嵌函数)的,返回值也是一个非常绕的情况,那么下面就用例子来帮助快速理解装饰器的具体的情况。
#funA 作为装饰器函数
def funA(fn):
#...
fn() # 执行传入的fn参数
#...
return '...'
@funA
def funB():
#...
实际上,上面程序完全等价于下面的程序:
def funA(function):
# function() 执行前的上文
function() # 执行传入的function参数,这里也就是 funB
# function() 执行后的下文
return '...'
def funB():
#.
funB = funA(funB) # 注意这里的是 funB 而不是 funB() 没有括号()的
funB
作为对象传递给了funA
的function
参数,而且在执行 function()
这个函数的前后,都可以添加装饰用的程序,如运行前输出文字,打个招呼“Hello”,运行完 function()
,则输出“The programing is successful.” 等等。
理解装饰器的返回值
# test.py
#funA 作为装饰器函数
def funA(function):
print("这是装饰的上文")
print(function.__name__+"函数返回值: ",end='')
function() # 执行传入的fn参数
print("这里是装饰下文")
return "装饰器函数的返回值,这里是一个字符串变量,而不是一个函数对象"
@funA
def funB():
print("学习 Python")
print(funB)
#print(funB())
显然,被“
@函数
”修饰的函数不再是原来的函数,而是被替换成一个新的东西(取决于装饰器的返回值),即如果装饰器函数的返回值为普通变量,那么被修饰的函数名就变成了变量名;同样,如果装饰器返回的是一个函数的名称,那么被修饰的函数名依然表示一个函数。
而这里的 return
返回值是一个字符串变量,那么返回值传递给了 funB
,这一过程其实就是等同于 funB = "字符串变量"
,而如果是return function
,也就是 funB = function
这一过程,那么就是将函数对象传递给一个函数对象。
实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。
装饰带参数的函数
下面先举一个简单的例子来理解如何装饰带参数的函数,在装饰器函数中嵌套一个函数,该函数带有的参数个数和被装饰器修饰的函数相同。例如:
# test.py
def funA(function):
# 定义一个嵌套函数
def wrapper(args):
print("真正执行的函数其实是",args)
return wrapper
@funA
def funB(args):
print("funB():", a) # 这里没有被执行到,因为funB = funA(function)
funB(funB.__name__) # funB 的参数被传递给了 wrapper 的 args 参数
输出显示:
真正执行的函数其实是 wrapper
这里是用装饰器内的嵌套函数夺取了被装饰函数的参数,前提是嵌套函数参数要和被装饰函数的参数类型和数量要一致。
理解 wrapper
,就要理解 pass
关键字
上面装饰器函数运行逻辑如下所示:
def funA(function):
# 等同于 pass
pass
# 定义一个嵌套函数
#def wrapper(args):
# print("真正执行的函数其实是",args)
return wrapper # 直接运行到 return 这里
装饰多参数的函数
那么如果需要装饰多个函数且这多个函数的参数有可能不一致的情况,就需要使用未知数量参数 *
和关键字参数 **
传参了。
参考链接:https://blog.csdn.net/qq_42701659/article/details/123428242?spm=1001.2014.3001.5501
# test.py
def decorator(function): # 装饰器函数
# 定义一个嵌套函数
def wrapper(*args,**kwargs):
function(*args,**kwargs)
return wrapper
@decorator
def funA(args):
print(args," funA 的单个参数输出")
@decorator
def funB(name,args):
print(name,args)
funA(funA.__name__)
funB(funB.__name__," funB 的两个参数输出")
输出结果:
wrapper funA 的单个参数输出
wrapper funB 的两个参数输出
你运行一番,就有可能发现运行 funA()
和 funB()
其实都是在运行 wrapper()
,看似是运行 funA()
和 funB()
,是因为 wrapper
嵌套函数内有 function(*args,**kwargs)
等同于在运行 funA()
和 funB()
。必须记住嵌套函数(内嵌函数)的参数,是通过夺取被装饰的函数参数来的。
关于函数装饰器可以嵌套(内嵌/装饰器链)的问题
@funA
@funB
@funC
def fun():
#...
上面程序的执行顺序是从里到外/从下到上,所以它等效于下面这几行代码:
fun = funA( funB ( funC (fun) ) )
最核心点是下面的这几行,看懂了也就差不多了
@funA @funB @funC
fun fun fun
fun = funA( fun = funB( fun = funC(fun) ) )
fun = funA( funB ( funC (fun) ) )
装饰器函数必定返回
return
一个和被装饰函数同名的一个变量或函数对象,如 fun
装饰器嵌套函数(内嵌函数/装饰器链),代码演示:
# test.py
def funA(function): # 装饰器函数
# 定义一个嵌套函数
def funaWrapper():
print(" 3 执行 funA装饰器")
function()
return funaWrapper
def funB(function):
def funbWrapper():
print(" 2 执行 funB 装饰器")
function()
return funbWrapper
def funC(function):
def funcWrapper():
print(" 1 执行 funC 装饰器")
function()
return funcWrapper
@funA
@funB
@funC
def fun():
print(" 0 执行 fun()")
print(fun())
输出显示:
3 执行 funA装饰器
2 执行 funB 装饰器
1 执行 funC 装饰器
0 执行 fun()
None
深入理解装饰器间的嵌套(内嵌函数/装饰器链),代码演示:
# test.py
def funA(fun): # 装饰器函数
# 定义一个嵌套函数
def funaWrapper():
print(" 3 执行 funA装饰器")
result = funbWrapper()
return result
return funaWrapper
def funB(fun):
def funbWrapper():
print(" 2 执行 funB 装饰器")
result = funcWrapper()
return result
return funbWrapper
def funC(fun):
def funcWrapper():
print(" 1 执行 funC 装饰器")
result = fun()
return result
return funcWrapper
@funA
@funB
@funC
def fun():
print(" 0 执行 fun()")
return "-1 fun 被装饰函数的结果返回值"
print(fun())
print(fun)
print("fun 被 funA 赋值了,相当于 fun = funaWrapper")
这其中最核心的且必须要理解的步骤是
fun = funA(fun)
fun = funB(fun)
fun = funC(fun)
@funC
def fun(): # 这里的 def fun() 相当于让解释器去调用 fun 函数对象给装饰器 funC(fun)
pass
仅仅是传递了 fun
函数对象给 funC(fun)
,别被误导了,弄混了,那么又该如何理清装饰器间的嵌套逻辑,很简单,既然第一个装饰器是 funC
,那么就会得到 fun = funC(fun)
,之后就被第二个装饰器 funB
装饰,也就是
@funB
def fun(): # 这里是 funC 的 fun 传给装饰器 funB(fun)
pass
@funA
def fun(): # 这里是 funB 的 fun 传给装饰器 funA(fun)
pass
无参数装饰器的运行逻辑:
装饰器的执行逻辑 1:
必先执行装饰器函数且一定要有一个返回值
return
,后面可以执行带参数的包装器函数wrapper()
—— 可以代替原函数获取原函数的参数,包装器函数wrapper
的返回值return
可有可无,根据需要自行决定。
装饰器的执行逻辑 2:
装饰器函数利用上下文的程序来装饰被装饰的函数,其实就是将一些程序封装在一个嵌套的函数内(内嵌函数 / 包装器),而这个嵌套(内嵌)的函数内的上下文程序可以包裹一个执行状态的被装饰函数() —— 加了括号的()函数名
;
特别注意:装饰器函数必须必须要有一个函数返回值
return
,不管是整型,浮点型,还是字符串或者函数对象等等,都必须有一个return
给fun
,也就是fun = derorator()
;
代码演示如下:
错误 —— 报错
# test.py
def decorator(function):
def wrapper():
function()
# decorator 装饰器 无 返回值
@decorator
def test():
print("test")
print(test())
#print(test) # 将注释解除,运行一番
错误 —— 报错
# test.py
def decorator(function):
function()
# decorator 装饰器 无 返回值
@decorator
def test():
print("test")
print(test)
#print(test()) # 将注释解除,运行一番
下面返回的是函数对象,需要加括号 ()调用
正确 —— 成功运行
# test.py
def decorator(function):
def wrapper():
function()
return wrapper # 返回 包装器 函数对象 # 函数对象声明
@decorator
def test():
print("test")
print(test())
# Output
# test
# None
正确 —— 成功运行
# test.py
def decorator(function):
function()
return function
@decorator
def test():
print("test")
print(test())
# Output
# test
# test
# None
正确 —— 成功运行
# test.py
def decorator(function):
function()
def wrapper():
function()
return wrapper
@decorator
def test():
print("test")
print(test())
# Output
# test
# test
# None
下面返回的是整型、浮点、字符型变量,无需加括号 () 调用
正确 —— 成功运行
# test.py
def decorator(function):
function()
return 0
@decorator
def test():
print("test")
print(test)
# Output
# test
# 0
正确 —— 成功运行
# test.py
def decorator(function):
function()
return "字符串"
@decorator
def test():
print("test")
print(test)
# Output
# test
# 字符串
正确 —— 成功运行
# test.py
def decorator(function):
function()
return 1.00
@decorator
def test():
print("test")
print(test)
# Output
# test
# 1.0
装饰器的执行逻辑 3:
当在使用已经被装饰器装饰过的函数fun
时,例如 fun(100,200)
,以为是在使用调用 fun()
函数,那么就要注意这里的 fun()
,还是不是原来的 fun()
,有可能不是原来的 fun()
函数了,有可能是装饰器内的嵌套函数对象,比如 fun = wrapper
。
上面提到的和带参数的函数有着非常关键的联系,总结如下:
只要是带参数的函数,被装饰过了,就一定一定不可能是原来的函数了,肯定是被装饰器内的嵌套函数给替换了,因为原函数的参数传递给了嵌套函数,由嵌套函数来获取这些参数
参考链接:http://c.biancheng.net/view/2270.html
二、带参数的装饰器
# test.py
# 带参数的函数装饰器
def say_hello(country):
def wrapper(func):
def deco(*args, **kwargs):
if country == 'china':
print('你好!')
elif country == 'america':
print('hello')
else:
return
func(*args, **kwargs)
return deco
return wrapper
@say_hello('china')
def chinese():
print('我来自中国。')
@say_hello('america')
def america():
print('I am from America.')
chinese()
print('-'*20)
america()
输出显示:
你好!
我来自中国。
--------------------
hello
I am from America.
这里的 @say_hello(参数)
其实相当于 @wrapper
等同于
@wrapper
def function():
pass
三、类装饰器 —— 类可以作为装饰器
前面我们提到的都是让 函数作为装饰器去装饰其他的函数或者方法,那么可不可以让一个类发挥装饰器的作用呢?答案肯定是可以的,一切皆对象,函数和类本质没有什么不一样。类的装饰器是什么样子的呢?
class Decorator(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("decorator start")
self.f()
print("decorator end")
@Decorator
def func():
print("func")
func()
类装饰器实现方法
class Decorator: |
def __init__(self,fun): |
self.fun = fun ——> def Decorator():
|
p = Decorator(func)
#p
是类Decorator
的一个实例
p()
# 实现了__call__()
方法后,p
可以被调用
要使用类装饰器必须实现类中的__call__()方法,就相当于将实例变成了一个方法。
是用@
来调用类装饰器,会自动回传一个类的实例化对象,而且这个@
符号,会自动将被装饰的函数作为一个变量,存储对象,如@Decorator def func():
相当于 fun = Decorator(fun)
,函数装饰器也是如此,不过如果是直接用 p = Decorator(fun)
,这样来装饰 func 函数的,而不是用@
符号装饰的,那么就需要注意类装饰器内有没有 __call__()
函数