Python: 携带状态的闭包

zhengjie 6年前
   <p>在 Python 中,函数也是一个对象。因此,我们在定义函数时,可以再嵌套定义一个函数,并将该嵌套函数返回,比如:</p>    <pre>  <code class="language-python">frommathimportpow     defmake_pow(n):      definner_func(x):    # 嵌套定义了 inner_func          return pow(x, n)  # 注意这里引用了外部函数的 n      return inner_func      # 返回 inner_func  </code></pre>    <p>上面的代码中,函数 make_pow 里面又定义了一个内部函数 inner_func ,然后将该函数返回。因此,我们可以使用 make_pow 来生成另一个函数:</p>    <pre>  <code class="language-python">>>> pow2 = make_pow(2)  # pow2 是一个函数,参数 2 是一个自由变量  >>> pow2  <functioninner_funcat 0x10271faa0>  >>> pow2(6)  36.0  </code></pre>    <p>我们还注意到,内部函数 inner_func 引用了外部函数 make_pow 的自由变量 n ,这也就意味着,当函数 make_pow 的生命周期结束之后, n 这个变量依然会保存在 inner_func 中,它被 inner_func 所引用。</p>    <pre>  <code class="language-python">>>> del make_pow        # 删除 make_pow  >>> pow3 = make_pow(3)  Traceback (mostrecentcalllast):    File "<stdin>", line 1, in <module>  NameError: name 'make_pow' is not defined  >>> pow2(9)    # pow2 仍可正常调用,自由变量 2 仍保存在 pow2 中  81.0  </code></pre>    <p>像上面这种情况,一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,我们把该返回的内部函数称为 <strong>闭包(Closure)</strong> 。</p>    <p>在上面的例子中, inner_func 就是一个闭包,它引用了自由变量 n 。</p>    <h2><strong>闭包的作用</strong></h2>    <ul>     <li>闭包的最大特点就是引用了自由变量,即使生成闭包的环境已经释放,闭包仍然存在;</li>     <li>闭包在运行时可以有多个实例,即使传入的参数相同,比如:</li>    </ul>    <pre>  <code class="language-python">>>> pow_a = make_pow(2)  >>> pow_b = make_pow(2)  >>> pow_a == pow_b  False  </code></pre>    <ul>     <li>利用闭包,我们还可以模拟类的实例。</li>    </ul>    <p>这里构造一个类,用于求一个点到另一个点的距离:</p>    <pre>  <code class="language-python">frommathimportsqrt     class Point(object):      def__init__(self, x, y):          self.x, self.y = x, y         defget_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  </code></pre>    <p>用闭包来实现:</p>    <pre>  <code class="language-python">defpoint(x, y):      defget_distance(u, v):          return sqrt((x - u) ** 2 + (y - v) ** 2)         return get_distance     >>> pt = point(7, 2)  >>> pt(10, 6)  5.0  </code></pre>    <p>可以看到,结果是一样的,但使用闭包实现比使用类更加简洁。</p>    <h2><strong>常见误区</strong></h2>    <p>闭包的概念很简单,但实现起来却容易出现一些误区,比如下面的例子:</p>    <pre>  <code class="language-python">defcount():      funcs = []      for i in [1, 2, 3]:          def f():              return i          funcs.append(f)      return funcs  </code></pre>    <p>在该例子中,我们在每次 for 循环中创建了一个函数,并将它存到 funcs 中。现在,调用上面的函数,你可能认为返回结果是 1, 2, 3,事实上却不是:</p>    <pre>  <code class="language-python">>>> f1, f2, f3 = count()  >>> f1()  3  >>> f2()  3  >>> f3()  3  </code></pre>    <p>为什么呢?原因在于上面的函数 f 引用了变量 i ,但函数 f 并非立刻执行,当 for 循环结束时,此时变量 i 的值是3, funcs 里面的函数引用的变量都是 3,最终结果也就全为 3。</p>    <p>因此,我们应 尽量避免在闭包中引用循环变量,或者后续会发生变化的变量 。</p>    <p>那上面这种情况应该怎么解决呢?我们可以再创建一个函数,并将循环变量的值传给该函数,如下:</p>    <pre>  <code class="language-python">defcount():      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  </code></pre>    <h2><strong>小结</strong></h2>    <ul>     <li>闭包是携带自由变量的函数,即使创建闭包的外部函数的生命周期结束了,闭包所引用的自由变量仍会存在。</li>     <li>闭包在运行可以有多个实例。</li>     <li>尽量不要在闭包中引用循环变量,或者后续会发生变化的变量。</li>    </ul>    <h2><strong>参考资料</strong></h2>    <ul>     <li><a href="/misc/goto?guid=4959726234207247119" rel="nofollow,noindex">返回函数 – 廖雪峰的官方网站</a></li>     <li><a href="/misc/goto?guid=4959726234297895546" rel="nofollow,noindex">Why aren’t python nested functions called closures? – Stack Overflow</a></li>    </ul>    <p> </p>    <p>来自:http://python.jobbole.com/86846/</p>    <p> </p>