python之装饰器
预备知识:https://www.cnblogs.com/Zzbj/p/9495066.html
一、函数名的应用
# 1,函数名就是函数的内存地址,而函数名()则是运行这个函数。 def func(): return print(func) # 返回一个地址 # 2,函数名可以作为变量。 def func1(): print(666) f1 = func1 f2 = f1 f2() # 就等于func1() 此时执行函数 # 3,函数名可以作为函数的参数。 def func1(): print(666) def func2(x): x() func2(func1) # 输出666 func1作为实参传进func2中,此时在func2中x()等于func1() # 4,函数名可以当做函数的返回值。 def wraaper(): def inner(): print(666) return inner ret = wraaper() # 执行函数wraaper(),得到返回值inner赋值给ret,这个inner是函数名 ret() # 输出666,ret()等于inner() # (注意:这里要调用inner()只能是用ret(),因为inner()是wraaper()的嵌套函数,在全局命名空间中并没有声明,所以直接用inner()不能执行,) def func2(): print('in func2') def func3(x): print('in func3') return x f = func3(func2) # 给func3传进一个参数func2,返回func2赋值给f f() # f()等于func2() # 5,函数名可以作为容器类类型的元素。 def f1(): print('f1') def f2(): print('f2') def f3(): print('f3') l = [f1, f2, f3] d = {'f1': f1, 'f2': f2, 'f3': f3} # 调用 l[0]() # 输出 f1 d['f2']() # 输出 f2 def func1(): print('in func1') def func2(): print('in func2') def func3(): print('in func3') def func4(): print('in func4') l1 = [func1,func2,func3,func4] #调用 for i in l1: i() # 像上面的函数名这种,称为第一类对象。 # 第一类对象(first-class object)指: # 1.可在运行期创建 # 2.可用作函数参数或返回值 # 3.可存入变量的实体。 # (如果不明白,那就记住一句话,就当普通变量用)
二、闭包
1、定义 内层函数对外层函数(非全局)的变量的引用,这个内层函数就成为闭包。 在Python中,我们可以使用__closure__来检测函数是否是闭包。 有cell元素的是闭包。 例如: def func(): name = '番薯' def func2(): print(name) # 引用外层函数的变量 func2() print(func2.__closure__) # (<cell at 0x000002591EA794F8: str object at 0x000002591EACB500>) func() print(func.__closure__) # None 2、闭包的例子 2-1、这里并没有引用外层函数的变量,而是把外层函数的变量传给func2,所以不算闭包 def func1(): name = '番薯' def func2(arg): print(arg) func2(name) print(func2.__closure__) func1() 2-2、 def func1(): name = '番薯' def func2(): print(name) # 引用外层函数的变量,形成闭包 func2() print(func2.__closure__) func1() 2-3、 def func1(name): def func2(): print(name) # 引用外层函数的变量,形成闭包 func2() print(func2.__closure__) func1('番薯') 3、闭包的应用 3-1、把内部函数(闭包)当成返回值返回就可以使用闭包了 def func1(): name = '番薯' def func2(): print(name) return func2 # 把内部函数当成是返回值返回 ret = func1() # 把返回值赋值给变量ret ret() # 调用内部函数 3-2、多层嵌套的闭包 def func1(): def func2(): def func3(): print('func3') return func3 return func2 f2 = func1() # func2 f3 = f2() # func3 f3() 4、闭包的好处 可以在任何时间从外界访问到内部函数。 但是我们知道一个函数执行完毕后,这个函数中的变量以及局部命名空间中的内容都是会被销毁的。 在闭包中如果变量被销毁了,那内部函数就不能正常执行了。 所以一旦这个内部函数引用了外层函数中的变量形成了闭包,那么这个变量将不会随着外层函数的结束而销毁,它会在内存中保留。 也就是说,闭包可以保留变量的引用。
三、装饰器初识
1、开放封闭原则 软件设计的原则: 开闭原则, 又被成为开放封闭原则。 开放封闭原则是指对扩展代码的功能是开放的, 但是对修改源代码是封闭的。 这样的软件设计思路可以保证我们更好的开发和维护我们的代码。 比如:你开了一家老字号(下面的代码就相当于源码) def old_shop(): print('离百年老字号还差99年') old_shop() 然后你的老店要新增空调(相当于新增功能): def old_shop(): print('新增空调') print('离百年老字号还差99年') old_shop() 这样添加功能确实是可以的,但是违背了开放封闭原则,直接在源码上进行修改了。 那怎么办呢?新建一个函数不就好了 def old_shop_with_conditioner(): print('新增空调') print('离百年老字号还差99年') old_shop_with_conditioner() 但是,问题又来了,你的老字号很火,开了很多分店,每家分店都是调用了之前的old_shop函数开店, 那么你修改了之后,所有调用原来函数的分店都需要重新调用新的函数,这么做其实是很番薯的行为。 那么如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能呢? 可以利用闭包 def old_shop(): print('离百年老字号还差99年') def a(func): def b(): print('新增空调') func() return b ret = a(old_shop) # 内层的b函数 ret() 然后问题又来了,现在虽然没有直接修改源码,但是函数名还是改变了,那又怎么办? 重命名不就行了吗: def old_shop(): print('离百年老字号还差99年') def a(func): def b(): print('新增空调') func() return b old_shop = a(old_shop) # 内层的b函数 old_shop() 这样不就遵循了开发封闭原则了吗,即没有修改源码,扩展代码又是开放的,也没有改变函数原来的调用方式 2、装饰器语法糖 刚才上面的代码只是装饰器的原理和雏形,Python中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。 使用装饰器语法糖的写法,实现同样功能的代码如下: def a(func): # a是我们定义的装饰器函数,func是被装饰的函数(old_shop) def b(): print('新增空调') func() return b @a # 相当于把被装饰的函数old_shop当成参数传给a,然后把返回值b再重新赋值给被装饰的函数名old_shop def old_shop(): print('离百年老字号还差99年') old_shop() # 相当于调用了内层函数b
四、装饰器进阶
1、装饰带返回值的函数 def wrapper(func): def inner(): print('新功能') # 你要新增的功能 result = func() # 拿到被装饰函数的返回值 return result # 返回被装饰的函数的返回值 return inner @wrapper def f(): return 'Hello' ret = f() print(ret) 2、装饰带参数的函数 def wrapper(func): def inner(x, y): # 实际执行函数的参数 print('新功能') r = func(x, y) return r return inner @wrapper def my_sum(x, y): return x + y ret = my_sum(1, 2) # inner(1, 2) print(ret) 但是一般来说,我们把参数设置成动态参数会更便于拓展 若是三个数相加,或者四个数相加,只需要修改my_sum的参数就可以了 def wrapper(func): def inner(*args, **kwargs): print('新功能') r = func(*args, **kwargs) return r return inner @wrapper def my_sum(x, y, z): return x + y + z ret = my_sum(1, 2, 3) print(ret) 3、带参数的装饰器(也叫装饰器工厂) 装饰器如果要带参数的话,可以嵌套更多层: def outer(arg): def wrapper(func): def inner(*args, **kwargs): print('欢迎来到%s' % arg) func(*args, **kwargs) return inner return wrapper @outer('英雄联盟') # 会先执行outer,然后返回函数名wrapper,相当于@wrapper,在闭包内还能使用最外层的函数变量 def lol(): print('这里是召唤师峡谷') @outer('地下城与勇士') # @wrapper def dnf(): print('这里是阿拉德大陆') lol() dnf() 4、装饰器修复技术 被装饰的函数最终都会失去本来的__doc__ __name__等信息,就是说,如果这个函数被装饰了, 那么它里面的文档信息(注释信息,通常注释信息很重要,注释写明了改函数的功能和参数的信息等)就会消失, Python给我们提供了一个修复被装饰函数的工具,用于找回这些信息。 from functools import wraps def wrapper(func): @wraps(func) def inner(*args, **kwargs): print('这是新功能') func(*args, **kwargs) return inner @wrapper def f1(x, y): """ 这里写这个函数的主要功能 :param x: 这个参数的类型 :param y: 这个参数的类型 :return: 返回值 """ print('我是帅哥') print(f1.__doc__) # 打印这个函数的文档信息(注释内容) print(f1.__name__) # 打印这个函数名 5、多个装饰器装饰同一函数 def wrapper1(func): print('w1') def inner1(): print('inner1') return '<i>{}</i>'.format(func()) return inner1 def wrapper2(func): print('w2') def inner2(): print('inner2') return '<b>{}</b>'.format(func()) return inner2 @wrapper1 @wrapper2 def f1(): return "小明" ret = f1() print(ret) 结果: w2 w1 inner1 inner2 <i><b>小明</b></i> 分析: 在装饰阶段会直接执行装饰函数,并拿到返回值,即 @wrapper2 ---> wrapper2(f1) ---> print('w2') ---> return inner2 ---> 把变量f1重新指向inner2 @wrapper1 ---> wrapper1(f1)[此时的f1实际上是inner2] ---> print('w1') ---> return inner1 ---> 把变量f1重新指向inner1 然后执行f1()相当于执行inner1() print('inner1') return '<i>{}</i>'.format(func()) 此时的func是传进来的参数inner2,所以又去执行inner2 print('inner2') return '<b>{}</b>'.format(func()) 此时的func是传进来的参数f1[被装饰的f1],所以拿到返回值<b>小明</b>,拼接到inner1的返回值那里,最后 <i><b>小明</b></i>
五、两层且带参数的装饰器
""" 装饰器格式:@ + 函数名 装饰器原理:把被装饰的函数名传递到装饰器,然后把装饰器的返回值重新赋值给被装饰函数 """ 1、标准装饰器 def desc(desc): def decorate(cls): print("desc:%s, cls:%s" %(desc, cls)) return cls return decorate # 把normal传到desc,把返回值decorate重新赋值给normal # 下面执行normal()实际上是执行了decorate() @desc def normal(cls): pass normal("normal字符串") # desc:<function normal at 0x01E1E300>, cls:normal字符串 2、两层且带参数的装饰器 def desc(desc): def decorate(cls): print("desc:%s, cls:%s" %(desc, cls)) return cls return decorate # 先执行desc("测试函数"),得到decorate # 下面等于是:@decorate, 把normal传到decorate,把返回值cls重新赋值给normal @desc("测试函数") def test(): print("test") # 结果:desc:测试函数, cls:<function test at 0x01E1E270> @desc("测试类") class TestAPI(): pass # 结果:desc:测试类, cls:<class '__main__.TestAPI'>
六、数码暴龙进化装饰器
1、类装饰器 我们除了可以使用函数装饰函数外,还可以用类装饰函数。 """ 先理清一下装饰器的思路: 1、 def desc(func): def inner(*args, **kwargs): pass return inner @desc def test(): pass 这样的装饰器,其实就是: 1.执行装饰器函数desc,并把被装饰的函数test当中参数传进去,即desc(test) 2.将desc的返回值inner重新赋值给test,即test=inner 3.执行test()即执行 inner() 2、 def outter(x): def desc(func): def inner(*args, **kwargs): pass return inner return desc @outter('嘿嘿') def test(): pass 这样的装饰器,其实就是: 0.先执行outter('嘿嘿'),获得函数desc,装饰器其实也是@desc 1.执行装饰器函数desc,并把被装饰的函数test当中参数传进去,即desc(test) 2.将desc的返回值inner重新赋值给test,即test=inner 3.执行test()即执行 inner() 3、那么类装饰器就可以有两种 1.使用类实例化对象时候 __init__方法 2.使用调用对象的 __call__ 方法 """ # 1.使用类实例化对象时候 __init__方法 class Page(object): def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print('before............') print("args: %s" % args) res = self.func(*args, **kwargs) print('after............') return res @Page # 相当Page() 也就是实例化对象,即 @__init__(index),实例化完成后会返回一个对象 def index(name): # 此时的index就是Page的对象 print("Hello %s" % name) index('index') # 对象() 会调用 __call__ # 结果 """ before............ args: index Hello index after............ 相当于 def __init__(func): def __call__(*args, **kwargs): res = func(*args, **kwargs) return res return __call__ @__init__ def index(name): print("Hello %s" % name) """ # 2.使用调用对象的 __call__ 方法 class Page(object): def __init__(self, desc): self.desc = desc def __call__(self, func): def inner(*args, **kwargs): print("inner") res = func(*args, **kwargs) return res return inner # 先执行Page(实例化的参数")得到实例化对象 # @实例化对象,即 对象(), 即@__call__(index) @Page("实例化的参数") def index(name): # 此时的index就是inner print("Hello %s" % name) index('index') # 执行inner # 结果 """ inner Hello index """ 2、装饰类 上面所有的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。 可以使用装饰器,来批量修改被装饰类的某些方法 # 定义一个类装饰器 class D(object): def __call__(self, cls): class Inner(cls): # 重写被装饰类的f方法 def f(self): print('Hello 番薯') return Inner @D() class C(object): # 被装饰的类 # 有一个实例方法 def f(self): print("Hello world.") if __name__ == '__main__': c = C() c.f()
七、装饰器小结(重)
1、装饰器的标准结构 from functools import wraps def wrapper(func): # func:被装饰的函数 @wraps(func) # 把func指向的函数的__doc__、__name__等属性复制到inner上面 def inner(*args, **kwargs): # *args和**kwargs是被装饰函数的参数 print('新功能') r = func(*args, **kwargs) print('新功能也可以在这里') return r return inner @wrapper # 此时会执行wrapper,所以wrapper必须定义在这一行之前 def hello(): print('Hello World!') hello() 2、带参数的装饰器(也叫装饰器工厂) from functools import wraps def outer(k=None): def wrapper(func): @wraps(func) def inner(*args, **kwargs): if k == 'start': print('节目开始') r = func(*args, **kwargs) return r else: print('节目还未开始') return inner return wrapper @outer('start') def hello(): """这里是hello函数""" print('Hello World!') hello() print(hello.__doc__) print(hello.__name__) 3、多个装饰器同时装饰一个函数 """ 给Hello World!包两层标签, <div><p>Hello World!</p></div> """ from functools import wraps def wrapper1(func): # 包p标签 @wraps(func) def inner1(*args, **kwargs): r = func(*args, **kwargs) return '<p>{}</p>'.format(r) return inner1 def wrapper2(func): # 包div标签 @wraps(func) def inner2(*args, **kwargs): r = func(*args, **kwargs) return '<div>{}</div>'.format(r) return inner2 @wrapper2 @wrapper1 def hello(): return "Hello World!" print(hello())
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix