装饰器
写在前面的话:
想要搞懂装饰器,多翻阅大神的博客,自己动手将代码实例化。
下面的代码都是随便取的名字,为了方便阅读,所以后面用了很多大白话。
感谢各位先行者提供的经验之谈,如果侵权请告知,再次致谢。
这句话很重要:python一切皆对象!
一、装饰器前言
装饰器(Decorator)是一个装饰函数的函数,本质就是一个函数,用来在不修改被装饰函数的源代码与调用方式的前提下,对被装饰函数进行功能扩展的函数。掌握装饰器前需要学习以下4点知识:
- 函数的本质就是一个指向函数本体的内存地址,执行函数的方法为函数名();
- 闭包(closure)知识;
- 开放封闭原则(OCP,Open Closed Principle):对扩展是开放的,而对修改是封闭的;
- 函数的元组参数(*args)与字典参数(**kwargs)
二、装饰器初识
"""定义一个装饰器"""
def outer(func):
def inner():
print("这是中华人民共和国:", end="")
func()
return inner
@outer # >>> demo1 = outer(demo1)
def demo1(): #要被装饰的函数
print("湖南省")
demo1()
👆👆👆👆代码中,outer与inner组成的闭包函数构成了一个装饰器,对demo1函数进行装饰。这样就可以在不修改demo1的前提下,添加新的功能——打印“这是中华人民共和国:”这样一句话。
通过语法糖:@,如下:
@outer # @outer ==> demo1 = outer(demo1)
def demo1():
不难看出,outer函数实际上被当做对象赋值给了demo1这个变量,此时的demo1等于是调用了outer(),由于outer函数的返回值是inner,间接调用了inner函数,而在inner函数内部存在下面一句代码,
func()
"""
func是外部函数的参数,@将demo1作为实参返回给了outer函数的形参func
func() ==> demo1() 此时完成了对demo1的调用,实现了装饰demo1的功能
"""
demo1的代码功能实际上被内部函数所接收了,意味着装饰器成功的对demo1进行了修饰,完成了新功能的添加。
下面通过两幅图来进行说明:
三、装饰器深入
(1)对无参数无返回值的函数进行装饰
在二、装饰器初识里定义的装饰器,就是对于没有参数和返回值的函数进行装饰。由于被装饰的函数没有参数,装饰器只需接收函数名,内部函数不需要接收参数和返回值。
(2)对有参数,无返回值的函数进行装饰
"""定义一个装饰器"""
def outer(func):
def inner(name):
print("这是中华人民共和国:", end="")
func(name)
return inner
@outer # >>> demo1 = outer(demo1)
def demo1(name): #要被装饰的函数
print("山东省的%s" % name)
demo1("威海市")
对于有参数的函数进行装饰的时候,因为demo1有参数,意味着内部函数inner在调用demo1的时候同样需要该参数来补充代码的完整,因为函数的定义原则中,形参和实参必须是一一对应的,所以,在内部函数中func的"()"中也必须有对形参name的引用,否则系统会报错。
(3)对不定长参数的函数进行装饰
"""定义一个装饰器"""
def outer(func):
def inner(*args,**kwargs):
print("这是中华人民共和国:", end="")
func(*args,**kwargs)
return inner
@outer # >>> demo1 = outer(demo1)
def demo1(*args,**kwargs): #要被装饰的函数
print("山东省的%s" % args)
demo1("威海市")
与(2)的定义相比,形参变成了元祖参数*args,字典参数kwargs**,在demo1输入多个形参时,元祖参数会一一接收,因此,在内部函数中也需要定义同样的形参,具体引用实际也是对有参数的函数进行装饰。
(4)对拥有返回值的参数进行装饰
"""定义一个装饰器"""
def outer(func):
def inner(*args,**kwargs):
print("这是中华人民共和国:", end="")
return func(*args,**kwargs)
return inner
@outer
def demo1(name):
print("山东省的%s" % name)
return "武汉加油"
test1 = demo1("威海市")
print(test1)
被装饰的函数demo1具有了返回值,意味着demo1被执行的时候,最终会输出返回值。因此在内部函数里面,func()在引用demo1()函数的时候,如果func()没有返回值,那么demo1()的返回值不会被接收,于是会返回none,因此要在func()前面加上return,说明一下返回引用函数的返回值。
四、多层装饰器
# -*- coding: UTF-8 -*-
"""定义第一个装饰器"""
def decorator1(func):
print("这里是第一个装饰器")
def inner():
print("这是第一个装饰器的内部函数。")
func()
return inner
"""定义第二个装饰器"""
def decorator2(func):
print("这里是第二个装饰器")
def inner():
print("这是第一个装饰器的内部函数。")
func()
return inner
@decorator1 #decorator2(demo1)=decorator1(decorator2(demo1))
@decorator2 # ==> demo1=decorator2(demo1)
def demo1():
print("这是被装饰函数")
print("我需要修改一些功能")
demoOne()
👆👆👆👆👆👆代码从上而下定义了两个装饰器,对demo1()进行装饰,语法糖“@”的定义实际相当于被装饰的函数被装饰器函数包括在括号内形成新函数,由于形成的新函数再次被语法糖接收,形成新函数,向上检索时已经没有语法糖来接收,此时返回第一个语法糖内部函数,真正实现对被装饰函数的修改。
因此,当存在多个装饰器的时候,根据语法糖的顺序,被装饰函数优先接收最靠近自己的装饰器,因此最下面的装饰器外部函数的代码被执行了,此时又被向上相邻的语法糖接收,执行该语法糖连接的装饰器外部函数的代码,依次向上检索。
当第一个语法糖也被执行之后,就会对内部函数进行调用,完成第一个装饰器的功能后向下继续执行命令,依次完成下面的装饰器。
五、带参数的装饰器
将闭包函数嵌套在新的函数里面,实现参数的传入。
为下列装饰器传入参数:
第一步,定义一个不带参数的装饰器:
"""定义一个装饰器"""
def dec1(func):
print("这里添加了一个新功能")
def inner():
print("这里是对func的引用")
func()
return inner
@dec1
def func():
print("这里是func函数")
第二歩,将闭包函数嵌套到新的函数中:
"""定义一个装饰器"""
def Addargs(name):
print("Addargs是为了传入参数而创建的函数")
def dec1(func):
print("dec1添加了一个新功能并接收了:%s" % name)
def inner():
print("inner是对func的引用")
return func()
return inner
return dec1
@Addargs("我是装饰器的参数")
def func():
print("我是func函数")
return "我是func的返回值。"
func()
通过比较,可以看出带有形参的Addargs函数将闭包函数包括在内层函数里,与此同时,语法糖从原本的dec1换作了对Addargs的链接,同时Addargs后面传入需要用到的实参,这样原来的装饰器便可以接收到该实参。
每一个函数都有自己的内存地址,python里面一切皆对象,意味着一切都可以赋值给变量,一个函数赋值给了变量,这个变量就指向了引用该方法的内存地址,引用这个变量,等效于执行了该方法。
上述见解也许很浅薄,万望轻骂,欢迎指点!
感谢您的阅读~