python lambda 闭包-python闭包

本文分为如下几个部分什么是闭包

闭包与装饰器

闭包等价——偏函数

闭包等价——类

闭包等价——其他

闭包用于捕获状态值

闭包等价——协程

三种方法实现动态均值

什么是闭包

闭包是携带着一些自由变量的函数。

我们直接来看一个例子

def fun_out(a):

def fun_in(b):

return a + b

return fun_in

fun1 = fun_out(1)

其中fun1函数是一个闭包,它携带着fun_out中定义的a变量,值为1。运行程序的效果是这样的

>>> fun1 = fun_out(1)

>>> fun1(3)

4

>>> fun1(6)

7

>>> fun5 = fun_out(5)

>>> fun5(3)

8

>>> fun5(6)

11

fun1和fun5两个函数的定义相同,只是携带的自由变量不同,便成为了两个函数。由此闭包可以作为函数工厂,生产出功能类似,但是会有细微差别的函数。

闭包用起来非常直观,它反映了函数内调用一个变量的搜索路径,fun_in中调用某变量会先在fun_in函数定义的局部空间中开始搜索,可以找到b

如果在局部空间中找不到,则在更上层的局部空间中找,可找到a

之后在全局空间中搜索,即函数外定义的变量

如果还是找不到会去找python内置变量

如果还是找不到则抛出异常

闭包的特点就是,如果单独看fun_in函数的定义,不看周围环境,则a是未被定义的;而用这种方式返回则可以将a的值内置到函数中,这就是fun_in与fun1的区别。

我们可以查看闭包中绑定的自由变量

def fun_out(a):

def fun_in(b):

return a + b

return fun_in

>>> fun1 = fun_out(1)

>>> fun1.__closure__

(,)

另外,有两点需要注意。第一,下面这个fun_in不是闭包,因为其中没有调用外面的a值,调用__closure__会为None

def fun_out(a):

def fun_in(b):

return 1 + b

return fun_in

第二,下面这种情况a和c都会内置进fun_in中,其中c内置的值是3

def fun_out(a):

c = 1

def fun_in(b):

return a + b + c

c = 3

return fun_in

第三,注意区分下面这种情况

def fun_out(a):

return fun_in()

def fun_in():

return a

fun_out(1)

如果fun_in不是定义在fun_out里面,则fun_in在寻找变量a时,不会找fun_out的局部空间,而是直接到全局去找。如果代码只是像上面这样定义,则调用fun_out会报错,因为fun_in找不到a这个变量;除非在全局定义一个a才能找到。

闭包与装饰器

装饰器是闭包的一个应用,只是携带的自由变量是一个函数

def print1(func):

def wrapper(*args, **kw):

print(1)

return func(*args, **kw)

return wrapper

@print1

def print2():

print(2)

>>> print2()

1

2

print2 = print1(print2),所以装饰器是闭包的一种应用,自由变量是一个函数,传入和输出使用了相同的变量名。使用print2传入print1是如何调用的、返回的是什么。

闭包等价——偏函数

还是以这个闭包为例

def fun_out(a):

def fun_in(b):

return a + b

return fun_in

fun1 = fun_out(1)

下面使用偏函数实现

from functools import partial

def fun(a, b):

return a + b

>>> fun1 = partial(fun, b=1)

>>> fun1(3)

4

>>> fun5 = partial(fun, b=5)

>>> fun5(3)

8

偏函数即固定某些参数的取值,达到函数工厂的作用。

但是这种方法不够灵活,比如下面一种情况

def fun_out(a):

c = a ** 2

def fun_in(b):

return c + b

return fun_in

fun2 = fun_out(2)

之后我们要反复使用fun2这个函数, c只有在定义fun2时计算过,不会在之后的步骤中重复计算,而使用偏函数就无法达到这种效果。这种效果下面的类也是可以达到的。

闭包等价——类

还是上面的例子,这里定义一个类来实现

class Add:

def __init__(self, b):

self.b = b

def __call__(self, a):

return a + self.b

>>> add1 = Add(1)

>>> add1(3)

4

>>> add5 = Add(5)

>>> add5(3)

8

类相比于普通函数的一个好处是,类可以将一些变量内置进去,让类中定义的函数随意调用和修改。类相比于闭包的好处在于,函数运行结束后,可以随意调用修改属性(自由变量);比如一个递归函数,想查看这个函数被调用了几次。

闭包等价——其他

1.如果只是使用普通的函数,传入两个参数也可以达到类似的效果

def fun(a, b):

return a + b

fun(3, b=1)

但是调用时太麻烦了,应该不算实现了闭包的功能。

2.使用lambda表达式

def fun(a, b):

return a + b

>>> fun1 = lambda a: fun(a, 1)

>>> fun1(3)

4

>>> fun5 = lambda a: fun(a, 5)

>>> fun5(3)

8

相比于partial来说,lambda表达式会略显臃肿。

闭包用于捕获状态值

除了函数工厂,闭包还可以用于将函数与它的状态绑定,方便输出函数的状态和函数运行结果,比如输出函数被调用的次数

def fun_out():

count = 0

def fun_in(something):

nonlocal count

count += 1

print(count, something)

return fun_in

>>> fun1 = fun_out()

>>> fun1("first")

1 first

>>> fun1("second")

2 second

这种应用可以改写成类的形式但是不能用partial等来改写。不过还有另一种改写方式——协程。

闭包等价——协程

def fun():

count = 0

while True:

something = yield

count += 1

print(count, something)

>>> fun1 = fun()

>>> next(fun1)

>>> fun1.send("first")

1 first

>>> fun1.send("second")

2 second

对yield不了解的读者可以参考这篇文章

三种方法实现动态均值

我们要实现一个函数达到下面的效果

def running_avg(number):

pass

running_avg(10) # 10 --> 10 / 1

running_avg(20) # 15 --> (10 + 20) / 2

running_avg(30) # 20 --> (10 + 20 + 30) / 3

1.类实现

class Running_avg:

def __init__(self):

self.total = 0

self.count = 0

def __call__(self, number):

self.total += number

self.count += 1

return self.total / self.count

>>> running_avg = Running_avg()

>>> running_avg(10)

10.0

>>> running_avg(20)

15.0

>>> running_avg(30)

20.0

2.闭包实现

def initial():

total = 0

count = 0

def func(number):

nonlocal total

nonlocal count

total += number

count += 1

return total / count

return func

>>> running_avg = initial()

>>> running_avg(10)

10.0

>>> running_avg(20)

15.0

>>> running_avg(30)

20.0

3.协程实现

def Running_avg():

total = 0

count = 0

average = None

while True:

number = yield average

count += 1

total += number

average = total / count

>>> running_avg = Running_avg()

>>> next(running_avg)

>>> running_avg.send(10)

10.0

>>> running_avg.send(20)

15.0

>>> running_avg.send(30)

20.0

参考资料

下面是本文的参考资料这篇文章对什么是闭包解释的非常清楚

动态均值参考fluent python一书第16章

专栏信息

专栏目录:目录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值