是否可以修改 Python 中位于外部(封闭)但不在全局范围内的变量?
- 2024-11-28 08:37:00
- admin 原创
- 163
问题描述:
考虑这个例子:
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
我会得到一个,并且使用不能解决问题,因为不是全局的。UnboundLocalError
`global`b
Python 实现的是词法作用域,而不是动态作用域——就像几乎所有现代语言一样。这里的技术不允许访问调用者的变量——除非调用者恰好也是一个封闭函数——因为调用者不在作用域内。有关此问题的更多信息,请参阅如何从调用者访问变量,即使它不是封闭作用域(即实现动态作用域)?。
解决方案 1:
在Python 3上,使用nonlocal
关键字:
该
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]
解决方案 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
解决方案 3:
我对 Python 还不熟悉,但我读过一些相关内容。我相信你能得到的最好的方法类似于 Java 解决方法,即将外部变量包装在列表中。
def A():
b = [1]
def B():
b[0] = 2
B()
print(b[0])
# The output is '2'
编辑:我猜这在 Python 3 之前可能是正确的。看起来nonlocal
这就是你的答案。
解决方案 4:
不,你不能,至少从这个角度来说不行。
因为“set操作”会在当前作用域内创建一个新的名称,覆盖外面的名称。
解决方案 5:
简短的答案会自动起作用
我创建了一个 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>
,即全局第二个参数是要在给定范围内执行的字符串或代码对象
您也可以在程序内部调用它而无需参数来进行交互式 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,而是执行:exec("x")
如果您觉得使用exec()
不是您想要的,您可以通过更新真正的本地字典(而不是 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!
解决方案 6:
Python 3
我不知道__dict__
在 Python 3 中,当函数的外部空间不是全局空间 == 模块时(当函数是嵌套函数时就是这种情况),函数是否有一个属性可以给出函数的外部空间。
Python 2
但据我所知,在 Python 2 中没有这样的属性。因此,在 Python 2 中执行所需操作的唯一可能性是:
使用可变对象,正如其他人所说
def A() : b = 1 print 'b 在 B() == 之前', b
def B() :
b = 10
print 'b ==', b
return b
b = B()
print 'b after B() ==', b
一个()
结果
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
执行后的全局bA()
已被修改,可能不希望如此
仅当全局命名空间中存在标识符为b 的对象时,情况才会如此
解决方案 7:
我认为你不应该这么做。可以改变其封闭上下文中事物的函数是危险的,因为该上下文可能是在函数不知情的情况下编写的。
您可以使其明确,方法是将 B 设为公共方法,将 C 设为类中的私有方法(这可能是最好的方法);或者通过使用可变类型(例如列表)并将其明确传递给 C:
def A():
x = [0]
def B(var):
var[0] = 1
B(x)
print x
A()
解决方案 8:
对于任何稍后再考虑这个问题的人来说,一个更安全但更繁重的解决方法是。无需将变量作为参数传递。
def outer():
a = [1]
def inner(a=a):
a[0] += 1
inner()
return a[0]
解决方案 9:
你可以,但是你必须使用全局语句(当使用全局变量时,这不是一个真正好的解决方案,但它有效):
def A():
global b
b = 1
def B():
global b
print( b )
b = 2
B()
A()