资讯专栏INFORMATION COLUMN

Python: 携带状态的闭包

daryl / 1007人阅读

闭包

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

from math import pow

def make_pow(n):
    def inner_func(x):     # 嵌套定义了 inner_func
        return pow(x, n)   # 注意这里引用了外部函数的 n
    return inner_func      # 返回 inner_func

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

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

>>> pow2(6)
36.0

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

>>> del make_pow         # 删除 make_pow
>>> pow3 = make_pow(3)
Traceback (most recent call last):
  File "", line 1, in 
NameError: name "make_pow" is not defined
>>> pow2(9)     # pow2 仍可正常调用,自由变量 2 仍保存在 pow2 中
81.0

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

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

闭包的作用

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

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

>>> pow_a = make_pow(2)
>>> pow_b = make_pow(2)
>>> pow_a == pow_b
False

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

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

from math import sqrt

class Point(object):
    def __init__(self, x, y):
        self.x, self.y = x, y

    def get_distance(self, u, v):
        distance = sqrt((self.x - u) ** 2 + (self.y - v) ** 2)
        return distance

>>> pt = Point(7, 2)        # 创建一个点
>>> pt.get_distance(10, 6)  # 求到另一个点的距离
5.0

用闭包来实现:

def point(x, y):
    def get_distance(u, v):
        return sqrt((x - u) ** 2 + (y - v) ** 2)

    return get_distance

>>> pt = point(7, 2)
>>> pt(10, 6)
5.0

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

常见误区

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

def count():
    funcs = []
    for i in [1, 2, 3]:
        def f():
            return i
        funcs.append(f)
    return funcs

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

>>> f1, f2, f3 = count()
>>> f1()
3
>>> f2()
3
>>> f3()
3

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

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

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

def count():
    funcs = []
    for i in [1, 2, 3]:
        def g(param):
            f = lambda : param    # 这里创建了一个匿名函数
            return f
        funcs.append(g(i))        # 将循环变量的值传给 g
    return funcs

>>> f1, f2, f3 = count()
>>> f1()
1
>>> f2()
2
>>> f3()
3
小结

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

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

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

本文由 funhacks 发表于个人博客,采用 Creative Commons BY-NC-ND 4.0(自由转载-保持署名-非商用-禁止演绎)协议发布。
非商业转载请注明作者及出处。商业转载请联系作者本人。
本文标题为: Python: 携带状态的闭包
本文链接为: https://funhacks.net/2016/11/...

参考资料

返回函数 - 廖雪峰的官方网站

Why aren"t python nested functions called closures? - Stack Overflow

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44262.html

相关文章

  • python基础知识之函数初阶——闭包

    摘要:我们说触发了闭包的函数叫做闭包函数闭包最大的特点就是它可以被外层函数返回后赋值给一个变量,并且携带了外层函数内定义的变量例子如下变量为函数开辟的局部命名空间内定义的变量函数内引用了变量的内层函数名被当作返回值,此时闭包规则达成。 什么是闭包? 其实我们在使用函数过程中不经意间就会触发闭包,因为总会出于某种原因会在函数内引用或修改上一层函数的变量,这时就会触发闭包 那么什么是闭包?其实就...

    TIGERB 评论0 收藏0
  • 开个脑洞,如何使用 javascript 实现“仿函数”(Functor)?

    摘要:中的函数本身就是对象,可以携带自身状态,另外还有化等函数式编程的方法让函数缓存状态,基本上没有仿函数存在的必要。 Functor 仿函数(Functor)是 C++ 里面一个重要的概念,简而言之就是使用重载了 operator() 运算符的对象模仿函数的行为,带来的收益是仿函数可以携带自身状态,普通的 C++ 函数不是对象,做不到这一点。 js 中的函数本身就是对象,可以携带自身状态,...

    anRui 评论0 收藏0
  • python中关于闭包用法详解

      小编写这篇文章的主要目的,主要是来给大家介绍,关于python中,相关语法问题的解答,比如在python,我们会遇到闭包和装饰器不会用的情况,那么,下文就会来给大家做一个详细的解答。  *args与**kwarsg及闭包和装饰器  过程  先理解闭包,再理解装饰器,不要忘了不定长参数 deffunc():   msg='111'   deffunc1():   print(ms...

    89542767 评论0 收藏0
  • 《JavaScript语言精粹》 代码摘录

    摘要:最近在读这本评价颇高的语言精粹,其作者是的创造者,在业界颇有名气。 最近在读这本评价颇高的《JavaScript语言精粹》,其作者Douglas Crockford 是JSON的创造者,在业界颇有名气。以下是阅读过程中认为比较有用的摘录的代码,希望能对各位有所启发 自定义的method方法 Function.prototype.method = function(name,func...

    haitiancoder 评论0 收藏0
  • 学习笔记:JavaScript 闭包是怎么通过作用域链霸占更多内存

    摘要:闭包是怎么通过作用域链霸占更多内存的本文是作者学习高级程序设计第一小节的一点个人理解,详细教程请参考原教材。函数执行过程创建了一个函数的活动对象,作用域链的最前端指向这个对象。函数执行完毕返回值后执行环境作用域链和活动对象一并销毁。 JavaScript 闭包是怎么通过作用域链霸占更多内存的? 本文是作者学习《JavaScript 高级程序设计》7.2第一小节的一点个人理解,详细教程请...

    HmyBmny 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<