1.何为闭包?
def func(x):
def func1(y):
print(x+y)
return func1
f = func(1)
f(2)
函数里面再定义一个函数的,里面这个函数会用到外层函数传入进来的参数,那么把这个函数及用到的外层函数的参数这个整体称为闭包。
2.和普通函数有何区别?为什么要用闭包?
普通函数和lambda函数只能传递函数功能,不能传递可以保存到一个小空间的数据;
例如你要计算 ax+b 的值
用普通函数是这样写
def func(x, a, b):
return a*x+b
func(1, 2, 3)
看起来没任何问题,但是如果你要计算多次要怎么写呢?
def func(x, a, b):
return a*x+b
func(1, 2, 3)
func(2, 2, 3)
func(3, 2, 3)
后面两个参数明明没有变化,你还得传,怎么样可以做到只传一次呢?
把 a b 换成全局变量不就不用传了嘛:
a = 2
b = 3
def func(x):
return a*x+b
func(1)
func(2)
这下是不用传了,但是你要先计算 10 次 2x+3,再计算 10 次 22x+33怎么办?难道你把全局的 a b 给改成 a=22,b=33吗?那计算完 22x+33 后再计算 2x+3 岂不是还要再改回来?
这时你又想到了默认参数:
def func(x, a=2, b=3):
return a*x+b
func(1)
func(2)
func(3, a=22, b=33)
func(2, a=22, b=33)
func(1)
这样写虽然不会出现全局变量总是被修改的情况,但是要计算多条线上的值时,你还是要传 a 和 b 的值。
这显然不美,那怎么办?你想到了对象,用对象不就完了嘛
class Fun(objdect):
def __init__(self, a, b);
self.a = a
self.b = b
def __call__(self, x):
return a*x+b
fun1 = Fun(2, 3)
fun1(1)
fun1(2)
fun2 = Fun(22, 33)
fun2(1)
fun2(2)
对象可以传递函数功能和数据,例如保存了 a 和 b 的值多次供特定函数调用,但是对象包含的东西太多,例如类里的一些自带属性和功能,如果要计算多条线的值 ,还得创建多个实例对象,显得有点浪费了,毕竟你就想保存个 a b 的值而已;
这种情况下你就可以用闭包了
def func(a, b):
def func1(x):
return a*x + b
return func1
func1 = func(2, 3)
func1(1)
func1(2)
func2 = func(22, 33)
func2(1)
func2(2)
用闭包的方式,就只有外层传进来的必要的参数加里面定义的函数的功能,不像对象有很多额外的东西,做某些事情时用闭包就很合适了,上面几种方式优缺点显而易见了吧。
有一个要注意的地方,如何在闭包里修改变量
x = 1
def func1():
x = 2
def func2():
# global x #如果要使用x=1,用global
nonlocal x #如果要使用x=2,用nonlocal
print("1:", x) #如果不使用上面两行声明变量,这里是找不到x变量的
x = 3
print("2:", x)
return func2
func = func1()
func()
结果:
1: 2
2: 3
总结:
匿名函数可以完成简单的功能,传递的是这个函数的引用,只有函数里面的功能;
普通函数可以完成较为复杂的功能,传递的也是这个函数的引用,只有函数里面的功能;
闭包能完成较为复杂的功能的同时,还可以保存一部分参数变量,传递的是闭包中的函数及数据,有函数的功能+变量数据;
对象能完成最复杂的功能,可以保存参数变量,传递的是很多数据和很多功能,有函数功能+变量数据。
3.闭包的另一个作用-装饰器
例如现在需求是这样的,公司项目里有一个陈年老模块,里面有这么几个方法
def func1():
print('func1')
def func2():
print('func2')
func1()
func2()
这两个方法现在已经被公司内部很多团队使用了,然后现在要在方法执行内部逻辑之前先执行一段新的业务代码,最终达到这样的效果:
def func1():
# 新逻辑1
# 新逻辑2
print('func1')
def func2():
# 新逻辑1
# 新逻辑2
print('func2')
func1()
func2()
你打算怎么做?你可以把这些新逻辑放到一个方法中,然后让所有使用了这些方法的团队在调用这些方法之前添加一行,你觉得改起来也不是那么麻烦,就是去别的部门一个一个去找别人说明原因,然后让别人改他们的代码嘛,说明原因后你还得意的补了一句:“这样我们基础业务部门就不用修改代码了呀!”
def func1():
print('func1')
def func2():
print('func2')
def func_new():
# 新逻辑1
# 新逻辑2
func_new()
func1()
func_new()
func2()
不出意外的话,你会被别人用拖鞋赶出来,别人代码都写好了,你让各个部门的人把他们的代码里凡是调用了 func1 和 func2 方法的上面全部加一行 func_new() ?就因为你自己部门的基础业务有调整,别闹了好么。
哈哈哈… 其实上面只是比个如,你肯定不会这样做的,这是最最最挫的方式了,只是顺便用这种通俗的方式来说明下修改目标是怎样的,所以你最终实现的目标还是上面展现的样子,但是不是这样的写法,例如下面这样:
def func1():
func_new()
print('func1')
def func2():
func_new()
print('func2')
def func_new():
# 新逻辑1
# 新逻辑2
func1()
func2()
这样改完后,别的使用这些方法的人代码就不用修改了,你也无需告诉他们你改了什么,因为对他们来说,没有任何变化,该怎么用还是怎么用。
然后你打算提交代码完工了,这时你领导走了过来,你打算给你的领导先看看你是怎么改的,领导看完后就语重心长的说了,你这样改完目的是达到了,但是不符合面向对象的开闭原则,还可以改善一下,例如用装饰器,说完领导接起电话去开会去了。
你回忆了一下面向对象的开闭原则,它规定软件中的对象(类,函数等)应该对于扩展是开放的,但对于修改是封闭的。简单理解就是已经实现的功能代码不允许被修改,但是可以被扩展。
你研究一番后用装饰器的方式又重新实现了上面的需求:
def set_new_func(f):
def call_func():
# 新逻辑1
# 新逻辑2
f()
return call_func
@set_new_func
def func1():
print('func1')
@set_new_func
def func2():
print('func2')
func1()
func2()
这样修改之后,当用户在调用 func1() 时,会先执行 call_func() 里的新逻辑,再执行 func1()。
这下性质就完全不一样了,在 func1 和 func2 内部逻辑不修改的情况下完成了新逻辑的添加,你不禁感叹这类操作在某些场景下确实很实用。这时领导开完会出来了,再次看了下你的新实现方式,看完后欣慰的对你说,对就是这样,小伙子不错!
4.装饰器的其它方式总结
4.1在目标方法的前面和后面执行逻辑
def set_new_func(f):
def call_func():
# 新逻辑1
f()
# 新逻辑2
return call_func
@set_new_func
def func1():
print('func1')
func1()
4.2带参数的方法
def set_new_func(f):
def call_func(name):
# 新逻辑1
f(name)
# 新逻辑2
return call_func
@set_new_func
def func1(name):
print('func1 %s' % name)
func1(name)
4.3对多个方法进行装饰的解释
def set_new_func(f):
print('开始装饰了')
def call_func():
# 新逻辑1
# 新逻辑2
f()
return call_func
@set_new_func
def func1():
print('func1')
@set_new_func
def func2():
print('func2')
func1()
func2()
注意,当 python 解释器在读到 @set_new_func 这一行时,就已经开始装饰了,上面第 2 行代码就会执行,尽管这时下面的 func1() 还没执行。
4.4对多个参数的方法进行装饰
def set_new_func(f):
def call_func(*args, **kwargs):
# 新逻辑1
f(*args, **kwargs) #自动拆包,如果不带*号就错了,成了传入两个参数,一个元组和一个字典
# 新逻辑2
return call_func
@set_new_func
def func1(name, *args, **kwargs):
print('func1 %s' % name)
print('args:', args)
print('kwargs:', kwargs)
print("*" * 10)
func1("test")
func1("test", 2)
func1("test", 2, num=3)
运行结果:
func1 test
args: ()
kwargs: {}
**********
func1 test
args: (2,)
kwargs: {}
**********
func1 test
args: (2,)
kwargs: {'num': 3}
**********
4.5带返回值的方法
def set_new_func(f):
def call_func(*args, **kwargs):
# 新逻辑1
result = f(*args, **kwargs)
# 新逻辑2
return result
return call_func
@set_new_func
def func1(name, *args, **kwargs):
print('func1 %s' % name)
return name
print(func1("test"))
4.6应用多个装饰器
def set_new_func(f):
print('开始装饰1')
def call_func(*args, **kwargs):
print("装饰器1")
result = f(*args, **kwargs)
# 新逻辑2
return result
return call_func
def set_new_func1(f):
print('开始装饰2')
def call_func(*args, **kwargs):
print("装饰器2")
return f(*args, **kwargs)
return call_func
@set_new_func1
@set_new_func
def func1(name, *args, **kwargs):
print('func1 %s' % name)
return name
print(func1("test"))
运行结果:
开始装饰1
开始装饰2
装饰器2
装饰器1
func1 test
test
注意开始装饰和实际扩展方法代码的运行顺序,例如下面这个示例:
def add_div(f):
def call_func(*args, **kwargs):
return "<div>%s</div>" % (f(*args, **kwargs))
return call_func
def add_a(f):
def call_func(*args, **kwargs):
return "<a>%s</a>" % (f(*args, **kwargs))
return call_func
@add_a
@add_div
def func1(name, *args, **kwargs):
print('func1 %s' % name)
return name
print(func1("test"))
运行结果:
func1 test
<a><div>test</div></a>
4.7用类来装饰
class SetFunc(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("新逻辑")
return self.func(*args, **kwargs)
@SetFunc
def func1(name, *args, **kwargs):
print('func1 %s' % name)
return name
print(func1("test"))
运行结果:
新逻辑
func1 test
test