Python闭包的两个注意事项

mpnx3600 2年前
   <h2>延迟绑定</h2>    <p>Python闭包函数所引用的外部自由变量是延迟绑定的。</p>    <pre>  <code class="language-python">In [2]: def multipliers():    ...:    return [lambda x: i * x for i in range(4)]   In [3]: print [m(2) for m in multipliers()]  [6, 6, 6, 6]  </code></pre>    <p>如以上代码: i 是闭包函数引用的外部作用域的自由变量, 只有在内部函数被调用的时候才会搜索变量 i 的值, 由于循环已结束, i 指向最终值3, 所以各函数调用都得到了相同的结果。</p>    <p>解决方法:</p>    <p>1) 生成闭包函数的时候立即绑定( <strong>使用函数形参的默认值</strong> ):</p>    <pre>  <code class="language-python">In [5]: def multipliers():      return [lambda x, i=i: i* x for i in range(4)]        ...:      In [6]: print [m(2) for m in multipliers()]  [0, 2, 4, 6]  </code></pre>    <p>如以上代码: 生成闭包函数的时候, 可以看到每个闭包函数都有一个带默认值的参数: i=i , 此时, 解释器会查找 i 的值, 并将其赋予形参 i , 这样在生成闭包函数的外部作用域(即外部循环中), 找到了变量 i , 遂将其当前值赋予形参 i 。</p>    <p>2) 使用 <strong>functools.partial</strong> :</p>    <pre>  <code class="language-python">In [26]: def multipliers():      return [functools.partial(lambda i, x: x * i, i) for i in range(4)]      ....:      In [27]: print [m(2) for m in multipliers()]      [0, 2, 4, 6]  </code></pre>    <p>如以上代码: 在有可能因为延迟绑定而出问题的时候, 可以通过 functools.partial 构造偏函数, 使得自由变量优先绑定到闭包函数上。</p>    <h2>禁止在闭包函数内对引用的自由变量进行重新绑定</h2>    <pre>  <code class="language-python">def foo(func):      free_value = 8      def _wrapper(*args, **kwargs):          old_free_value = free_value #保存旧的free_value          free_value = old_free_value * 2 #模拟产生新的free_value          func(*args, **kwargs)          free_value = old_free_value      return _wrapper  </code></pre>    <p>以上代码会报错, UnboundLocalError: local variable 'free_value' referenced before assignment , 以上代码本意是打算实现一个带有某个初始化状态( free_value )但在执行内部闭包函数的时候又可以按需变化出新的状态( free_value = old_free_value * 2 )的装饰器, 但内部由于发生了重新绑定, 解释器会将 free_value 看作局部变量, old_free_value = free_value 则会报错, 因为解释器认为 free_value 是没有赋值就被引用了。</p>    <p>解决:打算修改闭包函数引用的自由变量时, 可以将其放入一个list, 这样, free_value = [8] , free_value 不可修改, 但 free_value[0] 是可以安全的被修改的。</p>    <p>另外, Python 3.x增加了 nonlocal 关键字, 也可以解决这个问题。</p>    <p> </p>    <p>来自:http://python.jobbole.com/87500/</p>    <p> </p>