Python进阶:携带状态的闭包

以前也发过介绍闭包的文章,今天学习一下如何在闭包中携带状态。

作者:Ethan 

原文:https://funhacks.net/2016/11/17/closure/

闭包

在 Python 中,函数也是一个对象。因此,我们在定义函数时,可以再嵌套定义一个函数,并将该嵌套函数返回,比如:

  1. from math import pow

  2. def make_pow(n):

  3.    def inner_func(x):     # 嵌套定义了 inner_func

  4.        return pow(x, n)   # 注意这里引用了外部函数的 n

  5.    return inner_func      # 返回 inner_func

上面的代码中,函数 make_pow 里面又定义了一个内部函数 inner_func ,然后将该函数返回。因此,我们可以使用 make_pow 来生成另一个函数:

  1. >> > pow2 = make_pow(2)  # pow2 是一个函数,参数 2 是一个自由变量

  2. >> > pow2

  3. <function inner_func at 0x10271faa0 >

  4. >> > pow2(6)

  5. 36.0

我们还注意到,内部函数 inner_func 引用了外部函数 make_pow 的自由变量 n ,这也就意味着,当函数 make_pow 的生命周期结束之后, n 这个变量依然会保存在 inner_func 中,它被 inner_func 所引用。

  1. >> > del make_pow         # 删除 make_pow

  2. >> > pow3 = make_pow(3)

  3. Traceback(most recent call last):

  4.    File "<stdin>", line 1, in < module >

  5. NameError:

  6.    name 'make_pow' is not defined

  7. >> > pow2(9)     # pow2 仍可正常调用,自由变量 2 仍保存在 pow2 中

  8. 81.0

---|---

像上面这种情况,一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,我们把该返回的内部函数称为闭包( Closure )

在上面的例子中, inner_func 就是一个闭包,它引用了自由变量 n 。

闭包的作用

  • 闭包的最大特点就是引用了自由变量,即使生成闭包的环境已经释放,闭包仍然存在;

  • 闭包在运行时可以有多个实例,即使传入的参数相同,比如:

  1. >> > pow_a = make_pow(2)

  2. >> > pow_b = make_pow(2)

  3. >> > pow_a == pow_b

  4. False

  • 利用闭包,我们还可以模拟类的实例。

这里构造一个类,用于求一个点到另一个点的距离:

  1. from math import sqrt

  2. class Point(object):

  3.    def __init__(self, x, y):

  4.        self.x, self.y = x, y

  5.    def get_distance(self, u, v):

  6.        distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)

  7.        return distance

  8. >> > pt = Point(7, 2)        # 创建一个点

  9. >> > pt.get_distance(10, 6)  # 求到另一个点的距离

  10. 5.0

用闭包来实现:

  1. def point(x, y):

  2.    def get_distance(u, v):

  3.        return sqrt((x - u) ** 2 + (y - v) ** 2)

  4.    return get_distance

  5. >> > pt = point(7, 2)

  6. >> > pt(10, 6)

  7. 5.0

可以看到,结果是一样的,但使用闭包实现比使用类更加简洁。

常见误区

闭包的概念很简单,但实现起来却容易出现一些误区,比如下面的例子:

  1. def count():

  2.    funcs = []

  3.    for i in [1, 2, 3]:

  4.        def f():

  5.            return i

  6.        funcs.append(f)

  7.    return funcs

在该例子中,我们在每次 for 循环中创建了一个函数,并将它存到 funcs 中。现在,调用上面的函数,你可能认为返回结果是 1, 2, 3,事实上却不是:

  1. >> > f1, f2, f3 = count()

  2. >> > f1()

  3. 3

  4. >> > f2()

  5. 3

  6. >> > f3()

  7. 3

为什么呢?原因在于上面的函数 f 引用了变量 i ,但函数 f 并非立刻执行,当 for 循环结束时,此时变量 i 的值是3, funcs 里面的函数引用的变量都是 3,最终结果也就全为 3。

因此,我们应尽量避免在闭包中引用循环变量,或者后续会发生变化的变量

那上面这种情况应该怎么解决呢?我们可以再创建一个函数,并将循环变量的值传给该函数,如下:

  1. def count():

  2.    funcs = []

  3.    for i in [1, 2, 3]:

  4.        def g(param):

  5.            f = lambda: param    # 这里创建了一个匿名函数

  6.            return f

  7.        funcs.append(g(i))        # 将循环变量的值传给 g

  8.    return funcs

  9. >> > f1, f2, f3 = count()

  10. >> > f1()

  11. 1

  12. >> > f2()

  13. 2

  14. >> > f3()

  15. 3

小结

  • 闭包是携带自由变量的函数,即使创建闭包的外部函数的生命周期结束了,闭包所引用的自由变量仍会存在。

  • 闭包在运行可以有多个实例。

  • 尽量不要在闭包中引用循环变量,或者后续会发生变化的变量。

参考资料


题图:pexels,CC0 授权。

点击阅读原文,查看更多 Python 教程和资源。



阅读原文:http://mp.weixin.qq.com/s?__biz=MzAwNDc0MTUxMw==&mid=2649639864&idx=1&sn=691fe0541a31045bd7f8d78969bafb16&scene=0#wechat_redirect
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 86.复合 Cases 共享相同代码块的多个switch 分支 分支可以合并, 写在分支后用逗号分开。如果任何模式...
    无沣阅读 5,328评论 1 5
  • 这两天学习的时候对python中的闭包产生了兴趣,网上这篇文章写得很好,写在这里大家看看。 1. 闭包的概念 首...
    Alistair阅读 1,864评论 0 0
  • 闭包是功能性自包含模块,可以在代码中被传递和使用。Swift中的闭包与 C 和 Objective-C中的 blo...
    AirZilong阅读 2,750评论 0 2
  • 1、引言 最近在刷leetcode题的时候,遇到一个求最长回文子串的题目,于是,我写了如下的代码: 哎呀,写了两个...
    文哥的学习日记阅读 14,849评论 6 32
  • 这是一个两男一女的故事,放了一半的爱情,加了三分的欲望,添了一点动荡年代的不得已,熬成了一锅叫宿命的粥,任你心高命...
    e7ca5e512f65阅读 3,649评论 0 2

友情链接更多精彩内容