【问题标题】:Is it possible to modify variable in python that is in outer, but not global, scope?是否可以在 python 中修改外部但不是全局范围内的变量?
【发布时间】:2011-12-09 15:45:07
【问题描述】:

给定以下代码:

def A() :
    b = 1

    def B() :
        # I can access 'b' from here.
        print( b )
        # But can i modify 'b' here? 'global' and assignment will not work.

    B()
A()

B() 函数变量b 中的代码在外部范围内,但不在全局范围内。是否可以从 B() 函数中修改 b 变量?当然我可以从这里和print() 阅读它,但是如何修改它呢?

【问题讨论】:

  • 只要b 是可变的就可以。对b 的赋值将掩盖外部范围。
  • nonlocal 没有被向后移植到 2.x 是 Python 的尴尬之一。这是闭包支持的内在组成部分。
  • 看起来使用非本地或使用列表,如上所述,不适用于类。 Python 错误地假设变量将在作用域类中,而不是在函数类之一的内部。

标签: python python-2.7


【解决方案1】:

Python 3 上,使用 nonlocal keyword:

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

def foo():
    a = 1
    def bar():
        nonlocal a
        a = 2
    bar()
    print(a)  # Output: 2

Python 2 上,使用可变对象(如列表或字典)并改变值而不是重新分配变量:

def foo():
    a = []
    def bar():
        a.append(1)
    bar()
    bar()
    print a

foo()

输出:

[1, 1]

【讨论】:

  • 一个很好的方法是在外部范围内使用class nonlocal: pass。然后nonlocal.x可以在内部范围内赋值。
  • @kindall 非常感谢堆 :) 可能需要一个不同的名称,因为它破坏了前向兼容性。在 python 3 中,这是一个关键字冲突,会导致SyntaxError。也许NonLocal
  • 或者,因为它在技术上是一个类,Nonlocal? :-)
【解决方案2】:

您可以使用空类来保存临时范围。它就像可变的,但更漂亮。

def outer_fn():
   class FnScope:
     b = 5
     c = 6
   def inner_fn():
      FnScope.b += 1
      FnScope.c += FnScope.b
   inner_fn()
   inner_fn()
   inner_fn()

这会产生以下交互式输出:

>>> outer_fn()
8 27
>>> fs = FnScope()
NameError: name 'FnScope' is not defined

【讨论】:

  • 奇怪的是,类及其字段在内部函数中是“可见的”,但变量不是,除非您使用“nonlocal”关键字定义外部变量。
  • 在外部范围内初始化的字典上设置键也可以。
【解决方案3】:

我对 Python 有点陌生,但我读过一些关于此的内容。我相信你会得到的最好的方法类似于 Java 解决方法,即将你的外部变量包装在一个列表中。

def A():
   b = [1]
   def B():
      b[0] = 2
   B()
   print(b[0])

# The output is '2'

编辑:我想这在 Python 3 之前可能是正确的。看起来nonlocal 是你的答案。

【讨论】:

  • 呃,现在是真的。以 Python 3.9 为例。
【解决方案4】:

不,你不能,至少以这种方式。

因为“设置操作”会在当前作用域内创建一个新名字,它覆盖了外层。

【讨论】:

  • " 覆盖外层" 什么意思?在嵌套函数中定义名称为b的对象不会影响该函数外部空间中的同名对象
  • @eyquem 即无论赋值语句在哪里,都会在整个当前范围内引入名称。比如问题的示例代码,如果是: def C():print( b ) b=2 "b=2" 会在整个 C func 范围内引入名称 b,所以 print(b) 时,它会尝试在本地 C func 范围内获取 b 而不是外部的,本地 b 还没有初始化,所以会出错。
  • 这个“不,你不能”的答案只在 Python 2 上是正确的。在 3 上,你可以在分配给它之前在内部范围内将你的变量声明为 nonlocal my_variable
【解决方案5】:

不知道有没有一个函数的属性,当这个外层空间不是全局空间==模块时,函数的外层空间的__dict__ function 是一个嵌套函数,在 Python 3 中。

但在 Python 2 中,据我所知,没有这样的属性。

所以,做你想做的事情的唯一可能性是:

1) 使用可变对象,正如其他人所说的那样

2)

def A() :
    b = 1
    print 'b before B() ==', b

    def B() :
        b = 10
        print 'b ==', b
        return b

    b = B()
    print 'b after B() ==', b

A()

结果

b before B() == 1
b == 10
b after B() == 10

.

注意

Cédric Julien 的解决方案有一个缺点:

def A() :
    global b # N1
    b = 1
    print '   b in function B before executing C() :', b

    def B() :
        global b # N2
        print '     b in function B before assigning b = 2 :', b
        b = 2
        print '     b in function B after  assigning b = 2 :', b

    B()
    print '   b in function A , after execution of B()', b

b = 450
print 'global b , before execution of A() :', b
A()
print 'global b , after execution of A() :', b

结果

global b , before execution of A() : 450
   b in function B before executing B() : 1
     b in function B before assigning b = 2 : 1
     b in function B after  assigning b = 2 : 2
   b in function A , after execution of B() 2
global b , after execution of A() : 2

执行A()后的全局b已被修改,可能不是这样

只有在全局命名空间中存在标识符为 b 的对象时才会出现这种情况

【讨论】:

    【解决方案6】:

    自动工作的简短答案

    我创建了一个 python 库来解决这个特定问题。它是在 unlisence 下发布的,所以你可以随意使用它。您可以使用pip install seapie 安装它或在此处查看主页https://github.com/hirsimaki-markus/SEAPIE

    user@pc:home$ pip install seapie

    from seapie import Seapie as seapie
    def A():
        b = 1
    
        def B():
            seapie(1, "b=2")
            print(b)
    
        B()
    A()
    

    输出

    2
    

    参数的含义如下:

    • 第一个参数是执行范围。 0 表示本地 B(),1 表示父 A(),2 表示祖父 <module> aka global
    • 第二个参数是要在给定范围内执行的字符串或代码对象
    • 你也可以在你的程序中为 interactive shell 调用不带参数的函数

    长答案

    这更复杂。 Seapie 通过使用 CPython api 编辑调用堆栈中的帧来工作。 CPython 是事实上的标准,因此大多数人不必担心。

    如果您正在阅读本文,您可能最感兴趣的魔术词如下:

    frame = sys._getframe(1)          # 1 stands for previous frame
    parent_locals = frame.f_locals    # true dictionary of parent locals
    parent_globals = frame.f_globals  # true dictionary of parent globals
    
    exec(codeblock, parent_globals, parent_locals)
    
    ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),ctypes.c_int(1))
    # the magic value 1 stands for ability to introduce new variables. 0 for update-only
    

    后者将强制更新传递到本地范围。然而,局部范围的优化与全局范围不同,因此当您尝试直接调用新对象时,如果它们没有以任何方式初始化,则引入新对象会出现一些问题。我将从github页面复制一些方法来规避这些问题

    • 预先分配、导入和定义您的对象
    • 预先为您的对象分配占位符
    • 在主程序中将对象重新分配给自身以更新符号表:x = locals()["x"]
    • 在主程序中使用 exec() 而不是直接调用以避免优化。而不是调用 x do: exec("x")

    如果您觉得使用 exec() 不是您想要的,您可以 通过更新 true 本地字典(不是 locals() 返回的字典)来模拟行为。我会从https://faster-cpython.readthedocs.io/mutable.html复制一个例子

    import sys
    import ctypes
    
    def hack():
        # Get the frame object of the caller
        frame = sys._getframe(1)
        frame.f_locals['x'] = "hack!"
        # Force an update of locals array from locals dict
        ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame),
                                              ctypes.c_int(0))
    
    def func():
        x = 1
        hack()
        print(x)
    
    func()
    

    输出:

    hack!
    

    【讨论】:

      【解决方案7】:

      我认为你不应该想要这样做。可以在其封闭上下文中改变事物的函数是危险的,因为该上下文可能是在不知道函数的情况下编写的。

      您可以通过将 B 设为公共方法,将 C 设为类中的私有方法(可能是最好的方法)来使其显式化;或者通过使用可变类型(例如列表)并将其显式传递给 C:

      def A():
          x = [0]
          def B(var): 
              var[0] = 1
          B(x)
          print x
      
      A()
      

      【讨论】:

      • 如何在不知道函数内部嵌套函数的情况下编写函数?嵌套函数和闭包是它们所包含的函数的固有部分。
      • 你需要知道你所包含的函数的接口,但你不应该知道它们里面发生了什么。此外,您不能期望知道 他们 调用的函数中发生了什么,等等!如果一个函数修改了一个非全局或非类成员,它通常应该通过它的接口显式化,即将它作为一个参数。
      • Python 当然不会强迫你变得那么好,因此使用了 nonlocal 关键字 - 但你要谨慎使用它。
      • @Bob:我从来没有发现使用像这样的闭包是危险的,除了语言怪癖。将 locals 视为一个临时类,将本地函数视为该类的方法,并不比这更复杂。 YMMV,我猜。
      【解决方案8】:

      对于稍后查看此内容的任何人来说,更安全但更繁重的解决方法是。无需将变量作为参数传递。

      def outer():
          a = [1]
          def inner(a=a):
              a[0] += 1
          inner()
          return a[0]
      

      【讨论】:

        【解决方案9】:

        你可以,但你必须使用global statment(在使用全局变量时不是一个很好的解决方案,但它确实有效):

        def A():
            global b
            b = 1
        
            def B():
              global b
              print( b )
              b = 2
        
            B()
        A()
        

        【讨论】:

        • 查看我的回答,解释此解决方案的潜在缺点
        • 使用全局变量是完全不同的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-11-13
        • 2011-09-14
        • 2011-10-28
        • 1970-01-01
        相关资源
        最近更新 更多