python 闭包

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值