Python修改导入脚本命名空间

Modify namespace of importing script in Python

我希望我的模块中有一个函数能够访问和更改导入它的脚本的本地命名空间。这将启用如下功能:

>>> import foo
>>> foo.set_a_to_three()
>>> a
3
>>>

这在 Python 中可行吗?

要让模块访问脚本的命名空间,您必须将命名空间传递给函数。例如:

>>> import foo
>>> foo.set_a_to_three(globals(), locals())
>>> a
3

内部代码如下所示:

def set_a_to_three(globalDict, localDict):
    localDict['a'] = 3

旁注:在这种情况下,不需要全局字典,但如果需要修改全局变量,则需要使用全局字典。出于这个原因,我将其包含在此处以使功能可扩展。

但是,如 Eli Sadoff 所说,直接设置 a = 3 会更容易。将命名空间传递给其他程序不是一个好习惯。

Slack 团队 @omz 慷慨地提供了一个答案:

import inspect
def set_a_to_three():
    f = inspect.currentframe().f_back
    f.f_globals['a'] = 3

这提供了优于 __main__ 解决方案的优势,它可以在多个级别的导入中工作,例如,如果 a 导入 b 导入我的模块,foo , foo 可以修改 b 的全局变量,而不仅仅是 a 的(我认为)

不过,如果我没理解错的话,修改本地命名空间比较复杂。正如其他人指出的那样:

It breaks when you call that function from within another function. Then the next namespace up the stack is a fake dict of locals, which you cannot write to. Well, you can, but writes are ignored.

如果有更可靠的解决方案,将不胜感激。

免责声明:此方案只能修改全局变量,所以并不完美。请参阅 Is it good practice to use import __main__? 了解优缺点。

在foo.py中:

import __main__

def set_a_to_three():
    __main__.a = 3

__main__是给当前顶层程序起的名字。例如,如果您从模块 bar 导入 foo,则可以使用名称 __main__ 引用 bar。如果您从 bar 导入 foo,它最初是从模块 qux 导入的,那么 qux 就是 __main__,等等

bar -> foo
bar is __main__

qux -> bar -> foo
qux is __main__

如果你真的想修改 bar 中的变量而不是 qux,例如因为 qux 包含单元测试,你可以使用这样的东西:

import sys
import __main__
if "bar" in sys.modules:
    __main__ = sys.modules["bar"]

__main__中设置很容易,因为我们知道它的名字。

#foo.py
import sys

def set_a_to_three():
    sys.modules['__main__'].a = 3