1. 闭包
闭包定义:在函数嵌套的前提下;内部函数使用了外部函数的变量;并且外部函数返回了内部函数;我们把这个使用外部函数变量的内部函数称为闭包。
闭包有三大特点:
1.有内函数与外函数,即函数是嵌套的。
2.内函数使用了外函数的变量与参数。
3.外部函数的返回值为内部函数名。
例子:
def func01(): a=10 def func02(b): print(a) print(b) # return func02()#注意写括号就是直接调用,一般不这么做 return func02 #调用外函数,接受内函数 result=func01() #调用内函数 result(2)
#或者func01()(2)
2. 装饰器
采用了闭包的思路,在不改变原函数功能的情况下,为函数增添新的功能。下面给个例子:
def new_func(func): def wrapper(): print("新功能") func() return wrapper def old_func(): print("旧功能") #此时同时执行了新旧功能 old_func=new_func(old_func) #调用旧功能 old_func()#调用了new_func的wrapper()
分析以上代码:定义了两个函数,new_func的函数参数为func,将来调用它传入函数名。new_func(old_func)返回了wrapper函数名。最后一行old_func()调用了wrapper函数,而不是直接调用了定义的old_func()函数。
除了使用上面的方法外,python还提供了另外一种写法:@装饰器函数名写在被装饰函数的上方,实现两者的联系。如下:
def new_func(func): def wrapper(): print("新功能") res=func() return res return wrapper
@new_func def old_func(): print("旧功能") return True @new_func def old_func2(): print("旧功能2") return True #调用旧功能 r=old_func()#调用了new_func的wrapper() print("r=",r)#True r2=old_func2()
通过上面,基本对装饰器有了初步认识,但是仔细思考,发现如果def old_func()与def old_func2()加入函数参数又该怎么办处理。如果定义old_func时加入参数,那么装饰函数new_func里面的wrapper(参数)就应该加入参数(思考一下如何调用的)。比如下面这样:
def new_func(func): def wrapper(a): print("新功能") res=func(a) return res return wrapper @new_func def old_func(n): print("旧功能") print(n) return n**2 @new_func def old_func2(m): print("旧功能2") print(m) return m**2 #调用旧功能 r1=old_func(3)#调用了new_func的wrapper() print("r1=",r1)# r2=old_func2(4) print("r2=",r2)
运行结果如下:
似乎没有问题了,但是在newfunc装饰不同函数的情况下,如果被装饰函数参数个数不同呢?比如下面情况如何处理:
@new_func def old_func(n): print("旧功能") print(n) return n**2 @new_func def old_func2(m,k): print("旧功能2") print(m,k) return m+k
由于old_func与old_func2的参数不同,那么def wrapper(参数):中的参数个数似乎不好处理,别忘了还有元组形参与字典形参的写法。
def new_func(func): def wrapper(*args,**kwargs):#星号元组形参,把实参合并为一个元组,也可以不给传参数,字典实参也是 print("新功能") res=func(*args,**kwargs) return res return wrapper @new_func def old_func(n): print("旧功能") print(n) return n**2 @new_func def old_func2(m,k=2): print("旧功能2") print(m,k) return m+k #调用旧功能 r1=old_func(3)#调用了new_func的wrapper print("r1=",r1)# r2=old_func2(4,6) print("r2=",r2)
运行结果:
因此,不管有参没参,不管参数个数,统统使用wrapper(*args,**kwargs)这种写法,满足所有需求。
3. 带参数的装饰器
所谓带参数的装饰器语法格式:@函数名(参数)写在被装饰函数上方,又因为如果是装饰器的话@后面跟函数名,所以函数名(参数)必定返回一个函数名。如果不做说明,装饰器就指函数装饰器,这里引用网上的一个例子:
# 添加输出日志的功能 def logging(flag): def decorator(fn): def inner(num1, num2): if flag == "+": print("--正在努力加法计算--") elif flag == "-": print("--正在努力减法计算--") result = fn(num1, num2) return result return inner # 返回装饰器 return decorator # 使用装饰器装饰函数 @logging("+") def add(a, b): result = a + b return result @logging("-") def sub(a, b): result = a - b return result result = add(1, 2) print(result) result = sub(1, 2) print(result)
运行结果:
4. 多个装饰器
先直接看一段代码:
def new_func1(func): print(1) def wrapper(*args,**kwargs):#星号元组形参,把实参合并为一个元组,也可以不给传参数,字典实参也是 print(2) print("新功能1") res=func(*args,**kwargs) print(3) return res print(4) return wrapper def new_func2(func): print(5) def wrapper(*args,**kwargs):#星号元组形参,把实参合并为一个元组,也可以不给传参数,字典实参也是 print(6) print("新功能2") res=func(*args,**kwargs) print(7) return res print(8) return wrapper @new_func2 @new_func1 def old_func(n): print(9) print("旧功能") return n**2
右键运行程序后猜一猜结果,结果如下:
也就是说加上了@装饰器后,定义的函数有些代码执行了。换句话说,装饰器函数在被装饰函数定义好后立即执行。多个装饰器的调用顺序是自下往上的(装饰器装饰函数时的上下顺序)。再看下面这个例子:
""" 多个装饰器装饰的顺序是从里到外(就近原则),而调用的顺序是从外到里(就远原则) 装饰器函数在被装饰函数定义好后立即执行。多个装饰器的调用顺序是自下往上的(装饰器装饰函数时的上下顺序)。 """ def new_func1(func): print(1,end=" ") def wrapper1(*args,**kwargs):#星号元组形参,把实参合并为一个元组,也可以不给传参数,字典实参也是 print(2,end=" ") print("新功能1",end=" ") res=func(*args,**kwargs) print(3,end=" ") return res print(4,end=" ") return wrapper1 def new_func2(func): print(5,end=" ") def wrapper2(*args,**kwargs):#星号元组形参,把实参合并为一个元组,也可以不给传参数,字典实参也是 print(6,end=" ") print("新功能2",end=" ") res=func(*args,**kwargs) print(7,end=" ") return res print(8,end=" ") return wrapper2 @new_func2 @new_func1 def old_func(n): print(9,end=" ") print("旧功能",end=" ") return n**2 #调用旧功能 r1=old_func(3)#调用了new_func的wrapper() print("r1=",r1)#
至于为什么先输出1 4 5 8上面的例子与图讲了,接下来我们分析执行最后两句代码后的执行顺序。@new_func1看作a=new_func1(old_func),这里a的值为new_func1中的wrapper1的函数地址值,(还未执行);@new_func2看作 b=new_func2(a),此时将wrapper1函数名(地址值)传给了new_func2,此时b的值为new_func2中的wrapper2的地址值(函数名)。最后相当于执行了b(3)。此时,函数名+括号,函数内容开始执行了,首先执行wrapper2(3),所以输出了6 新功能2,然后到这里了,res=func(*args,**kwargs),这一句中的func就是a值,也就是执行a(3),即wrapper1(3),此时输出2 新功能1,又到这里了res=func(*args,**kwargs),此刻func是old_func,也就是old_func(3),输出 9 旧功能(然后返回给3**3的结果给res),然后就行执行wrapper1中的代码,输出3,然后返回res给了wrapper2中的res,然后输出 7,最后返回res给r1,输出r1=9.
再看下面这个例子可能更懂一些了:
def decorator1(func): def wrapper(): print('decorator1 start') func() print('decorator1 end') return wrapper def decorator2(func): def wrapper(): print('decorator2 start') func() print('decorator2 end') return wrapper @decorator1 @decorator2 def my_func(): print('Original function') my_func()
在Python中,装饰器可以串联在一起使用,即一个函数可以同时应用多个装饰器。但是,需要注意的是装饰器应用顺序是从下往上执行。在上面的代码中,我们定义了两个装饰器decorator1
和decorator2
,其中decorator1
在decorator2
之上。然后我们将它们同时应用在my_func
函数上面,并且在my_func
函数中添加一些输出语句。当我们调用my_func
函数时,它将输出以下内容:
5. 类装饰器装饰函数
类也可以作为装饰器,可以装饰函数。
class Decorator: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("Decorator") self.func(*args, **kwargs) @Decorator def some_function(): print("Some function") print(type(some_function))#Decorator类型,相当于some_function=Decorator(some_function);
some_function.__call__() #或者 some_function()
运行结果:
6. 函数装饰器装饰类
函数装饰器装饰类是一种很有趣的技巧,可用于编写更具有动态性和灵活性的代码。
def add_method(cls): def add(self, x, y): return x + y cls.add = add # 在类上添加新方法 return cls @add_method class MyClass: def __init__(self, x, y): self.x = x self.y = y my_obj = MyClass(5, 10) print(my_obj.add(2, 3)) # Output: 5
在上面的代码中,add_method
函数接收一个类作为参数,然后定义了一个新方法add
并将它添加到类上面。使用@add_method
装饰器应用这个函数到MyClass
类上面。最后,我们创建了一个MyClass
对象,并且使用add
方法。因为我们已经在类上定义了add
方法,所以我们可以直接使用它。
7. 类装饰器装饰类
Python类也可以作为装饰器来装饰其他类。这种装饰器常常被称为类装饰器。这种装饰器不同于函数装饰器,因为它们可以为类添加属性和方法,并且可以在整个类的层次结构中重复使用。例子如下:
class AddMethod: def __init__(self, cls): self.cls = cls def __call__(self): self.cls.new_method = lambda x, y: x + y return self.cls @AddMethod class MyClass: def __init__(self, x, y): self.x = x self.y = y my_obj = MyClass() print(my_obj.new_method(3, 5)) # Output: 8
在上面的代码中,我们首先定义了一个AddMethod
类,该类接收一个类作为参数并对其进行修改。在__call__
方法中,我们将类的new_method
属性设置为一个新的lambda函数。然后我们将整个类返回。接着,我们使用@AddMethod
装饰器应用了该类装饰器到MyClass
类上面。最后,我们创建了一个MyClass
对象,并且使用添加的new_method
方法。
小结:使用带参数的装饰器,其实也就是再在外层嵌套一层函数,注意@后面一定是装饰器(函数名)或者返回值是装饰器(函数名)。如果有多个装饰器,要注意多个装饰器的调用顺序。另外,要注意wrapper的返回值与func()一致,不能增加或者减少返回值的个数(装饰器不改变原函数功能,所以返回值不能改可以直接return func(*args,**kwargs)。文章对函数作为装饰器装饰函数,也装饰了类,而且将类也作为装饰器对类进行装饰。关于闭包与装饰器介绍的可能不全面,但是可以作为基础入门。
不足或存在错误之处,欢迎指正与评论!觉得有用,请帮忙点个赞!
参考资料:
https://blog.csdn.net/zzh_love/article/details/129007703
https://zhuanlan.zhihu.com/p/451374770
https://blog.csdn.net/weixin_44992737/article/details/125868592
https://www.jb51.net/article/258254.htm
标签:闭包,return,python,func,print,new,装饰,def From: https://www.cnblogs.com/wancy/p/17498300.html