是否可以修改 python 中位于外部(封闭的)而非全局作用域中的变量?

考虑一下这个例子:

def A():
b = 1
def B():
# I can access 'b' from here.
print(b)
# But can i modify 'b' here?
B()
A()

对于 B函数中的代码,变量 b位于非全局、封闭(外部)作用域中。如何从 B内修改 b?如果我直接尝试它,我会得到一个错误,并且使用 global不能解决这个问题,因为 b不是全局的。


Python 实现了 词汇上的,不是动态的作用域——就像几乎所有现代语言一样。这里的技术将 没有允许访问调用方的变量(除非调用方碰巧也是一个封闭函数) ,因为调用方不在作用域内。有关此问题的更多信息,请参见 如何从调用者访问变量,即使它不是一个封闭范围(即,实现动态范围) ?

91260 次浏览

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

因为“ set 操作”将在当前作用域中创建一个新名称,该名称涵盖外部作用域。

巨蟒3,使用 nonlocal关键字:

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

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

巨蟒2上,使用一个可变对象(比如列表或 dict)并对值进行变异,而不是重新分配一个变量:

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


foo()

产出:

[1, 1]

我对 Python 还不太熟悉,但我读过一些关于这方面的资料。我相信最好的解决方法是类似于 Java 的解决方案,即将外部变量包装在一个列表中。

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


# The output is '2'

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

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

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

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


A()

你可以,但是你必须使用 全球声明(在使用全局变量时不是一个真正好的解决方案,但是它可以工作) :

def A():
global b
b = 1


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


B()
A()

我不知道是否有一个函数的属性给出了函数外部空间的 __dict__,当这个外部空间不是全局空间 = = 模块时,在 Python 3中,当函数是一个嵌套函数时,就是这种情况。

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

所以你唯一能做的就是:

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的对象时才会出现这种情况

您可以使用一个空类来保存一个临时作用域。

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

对于任何人来说,在更安全但更沉重的解决方案看到这一点很晚。不需要将变量作为参数传递。

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

这个简短的回答会自动生效

我创建了一个 Python 库来解决这个特定的问题。它是在无意识状态下释放的,所以你可以随心所欲地使用它。你可以安装它与 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>即全局 <module>
  • 第二个参数是要在给定作用域中执行的字符串或代码对象
  • 也可以在没有 交互式外壳程序参数的情况下调用它

长话短说

这事更复杂。Seapie 使用 CPythonapi 编辑调用堆栈中的帧。Cython 是行业标准,所以大多数人不必担心。

如果你正在阅读这篇文章,你可能最感兴趣的关键词如下:

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 = local ()[“ x”]
  • 在主程序中使用 exec () ,而不是直接调用以避免优化

如果你觉得使用 exec()不是你想要的东西,你可以 通过更新 没错本地字典(而不是局部变量()返回的字典)来模拟这种行为。我将从 < a href = “ https://fast-cpython.readthedocs.io/mutable.html”rel = “ nofollow norefrer”> 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!