【问题标题】:What limitations have closures in Python compared to language X closures?与语言 X 闭包相比,Python 中的闭包有哪些限制?
【发布时间】:2010-09-13 14:06:22
【问题描述】:

其中 X 是任何支持某种闭包风格的编程语言(C#、Javascript、Lisp、Perl、Ruby、Scheme 等)。

Closures in Python 中提到了一些限制(与 Ruby 的闭包相比),但该文章已经过时,现代 Python 中不再存在许多限制。

查看具体限制的代码示例会很棒。

相关问题

【问题讨论】:

    标签: python closures


    【解决方案1】:

    我看到人们在使用 Python 时遇到的唯一困难是当他们尝试将变量重新分配等非功能性功能与闭包混合使用时,当这不起作用时会感到惊讶:

    def outer ():
        x = 1
        def inner ():
            print x
            x = 2
        return inner
    outer () ()
    

    通常只是指出一个函数有自己的局部变量就足以阻止这种愚蠢。

    【讨论】:

    • @Moe 谢谢; @JF 完全正确。闭包的工作方式与任何其他函数一样,但出于某种原因,人们认为在分配变量时应该发挥作用。
    • 我的评论太大了。我已将其发布为答案。
    【解决方案2】:

    目前最重要的限制是您不能分配给外部范围变量。换句话说,闭包是只读的:

    >>> def outer(x): 
    ...     def inner_reads():
    ...         # Will return outer's 'x'.
    ...         return x
    ...     def inner_writes(y):
    ...         # Will assign to a local 'x', not the outer 'x'
    ...         x = y
    ...     def inner_error(y):
    ...         # Will produce an error: 'x' is local because of the assignment,
    ...         # but we use it before it is assigned to.
    ...         tmp = x
    ...         x = y
    ...         return tmp
    ...     return inner_reads, inner_writes, inner_error
    ... 
    >>> inner_reads, inner_writes, inner_error = outer(5)
    >>> inner_reads()
    5
    >>> inner_writes(10)
    >>> inner_reads()
    5
    >>> inner_error(10)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 11, in inner_error
    UnboundLocalError: local variable 'x' referenced before assignment
    

    除非另有声明,否则在本地范围(函数)中分配的名称始终是本地的。虽然有一个“全局”声明来声明一个全局变量,即使它被分配给它,但对于封闭的变量还没有这样的声明——然而。在 Python 3.0 中,有(将有)'nonlocal' 声明可以做到这一点。

    您可以同时使用可变容器类型来解决此限制:

    >>> def outer(x):
    ...     x = [x]
    ...     def inner_reads():
    ...         # Will return outer's x's first (and only) element.
    ...         return x[0]
    ...     def inner_writes(y):
    ...         # Will look up outer's x, then mutate it.      
    ...         x[0] = y
    ...     def inner_error(y):
    ...         # Will now work, because 'x' is not assigned to, just referenced.
    ...         tmp = x[0]
    ...         x[0] = y
    ...         return tmp
    ...     return inner_reads, inner_writes, inner_error
    ... 
    >>> inner_reads, inner_writes, inner_error = outer(5)
    >>> inner_reads()
    5
    >>> inner_writes(10)
    >>> inner_reads()
    10
    >>> inner_error(15)
    10
    >>> inner_reads()
    15
    

    【讨论】:

    • nonlocal 正如你所提到的解决了这个问题。带有__call__ 的内部类也可以解决它(但带有列表的版本更简洁)。
    【解决方案3】:

    @约翰·米利金

    def outer():
        x = 1 # local to `outer()`
    
        def inner():
            x = 2     # local to `inner()`
            print(x)
            x = 3
            return x
    
        def inner2():
            nonlocal x
            print(x)  # local to `outer()`
            x = 4     # change `x`, it is not local to `inner2()`
            return x
    
        x = 5         # local to `outer()`
        return (inner, inner2)
    
    for inner in outer():
        print(inner()) 
    
    # -> 2 3 5 4
    

    【讨论】:

      【解决方案4】:

      在 Python 3 中通过 nonlocal 语句修复:

      nonlocal 语句导致列出的标识符引用之前绑定的最近封闭范围内的变量,不包括全局变量。这很重要,因为绑定的默认行为是首先搜索本地命名空间。该语句允许封装代码重新绑定全局(模块)范围之外的局部范围之外的变量。

      【讨论】:

      • 我的评论太大了。我已将其发布为答案。
      • 嗯...大概非局部变量在闭包的所有实例之间共享?让它更像一个静态/类变量?
      【解决方案5】:

      注释@Kevin Little's answer 以包含代码示例

      nonlocal在python3.0上并没有完全解决这个问题:

      x = 0 # global x
      def outer():
          x = 1 # local to `outer`
          def inner():
              global x
              x = 2 # change global
              print(x) 
              x = 3 # change global
              return x
          def inner2():
      ##        nonlocal x # can't use `nonlocal` here
              print(x)     # prints global
      ##        x = 4      # can't change `x` here
              return x
          x = 5
          return (inner, inner2)
      
      for inner in outer():
          print(inner())
      # -> 2 3 3 3
      

      另一方面:

      x = 0
      def outer():
          x = 1 # local to `outer`
          def inner():
      ##        global x
              x = 2
              print(x) # local to `inner` 
              x = 3 
              return x
          def inner2():
              nonlocal x
              print(x)
              x = 4  # local to `outer`
              return x
          x = 5
          return (inner, inner2)
      
      for inner in outer():
          print(inner())
      # -> 2 3 5 4
      

      它适用于 python3.1-3.3

      【讨论】:

      • 我知道这是一个非常古老的答案,但对于 python3.2 来说这是完全错误的 - 您可以在第一个示例中完全使用 nonlocal 并获得预期的结果。我没有 python3.0 构建来检查它当时是否不同,但如果是,我会认为它是一个错误,因为PEP 没有说明它应该不同。唯一(明显)的限制是您不能在同一范围内使用 global xnonlocal x
      • @l4mpi:如果您取消注释 nonlocal,它将在 python3.0 上生成 SyntaxError: no binding for nonlocal 'x' found。但它适用于 python3.1-3.3
      • 啊,谢谢你的回答。现在这似乎更像是一个错误,虽然我无法在 3.1 发行说明中找到错误报告或注释...
      【解决方案6】:

      在 3.0 之前,更好的解决方法是将变量作为默认参数包含在封闭的函数定义中:

      定义 f() x = 5 定义 g(y, z, x=x): x = x + 1

      【讨论】:

      • 外部x 的值将始终为5
      【解决方案7】:

      与 Javascript 闭包相比,Python 闭包的一个限制(或“限制”)是它不能用于有效的数据隐藏

      Javascript

      var mksecretmaker = function(){
          var secrets = [];
          var mksecret = function() {
              secrets.push(Math.random())
          }
          return mksecret
      }
      var secretmaker = mksecretmaker();
      secretmaker(); secretmaker()
      // privately generated secret number list
      // is practically inaccessible
      

      Python

      import random
      def mksecretmaker():
          secrets = []
          def mksecret():
              secrets.append(random.random())
          return mksecret
      
      secretmaker = mksecretmaker()
      secretmaker(); secretmaker()
      # "secrets" are easily accessible,
      # it's difficult to hide something in Python:
      secretmaker.__closure__[0].cell_contents # -> e.g. [0.680752847190161, 0.9068475951742101]
      

      【讨论】:

      • 即使使用标准 OO 数据隐藏,您也可以使用 obj._Class__private_variable 获取 Python 类的 __private_variable。数据隐藏的重点是提供抽象,而不是安全性,如果您的客户端代码一心想要打破四处乱转。
      • "易于访问" - 如果您需要真正晦涩难懂的语法,那么它就不容易访问。通过指针操作和目标 ABI 中的对象布局知识,C++ 中的私有成员同样“易于访问”。信息隐藏并不是要让确定的人无法访问事物。这是关于使内部部件隐藏起来,避免无意中使用。就是这样。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-19
      • 2023-03-11
      • 2018-05-09
      • 2018-02-13
      • 2014-06-25
      • 1970-01-01
      相关资源
      最近更新 更多