如何从另一个模块更改模块变量?
假设我有一个叫做 bar
的包,它里面有一个文件 bar.py
:
a = None
def foobar():
print a
还有一个 __init__.py
文件:
from bar import a, foobar
然后我执行这个脚本:
import bar
print bar.a
bar.a = 1
print bar.a
bar.foobar()
这是我期待的结果:
None
1
1
这是我实际得到的结果:
None
1
None
有没有人能解释一下我哪里理解错了?
5 个回答
换句话说:这个误解其实很容易产生。在Python的语言参考中,有个比较隐晦的定义:使用对象而不是符号。我建议Python的语言参考能更清楚地说明这一点,而不是这么简略。
from
的形式并不会绑定模块名称:它会遍历标识符列表,查找每一个标识符在步骤(1)中找到的模块,并将这个名字绑定到本地命名空间中的对象。
但是:
当你导入时,你导入的是被导入符号的当前值,并将其添加到你定义的命名空间中。 你并不是在导入一个引用,而是实际上在导入一个值。
因此,要获取i
的更新值,你必须导入一个持有该符号引用的变量。
换句话说,导入并不像JAVA中的import
,C/C++中的external
声明,甚至也不是PERL中的use
子句。
相反,Python中的以下语句:
from some_other_module import a as x
更像是K&R C中的以下代码:
extern int a; /* import from the EXTERN file */
int x = a;
(注意:在Python的情况下,“a”和“x”本质上是对实际值的引用:你不是在复制整数,而是在复制引用地址)
这个问题的一个难点在于你有一个名为 bar/bar.py
的程序:当你使用 import bar
时,它会根据你在哪里调用,导入 bar/__init__.py
或 bar/bar.py
,这让人有点难以追踪哪个 a
是 bar.a
。
下面是它的工作原理:
理解发生了什么的关键是要明白在你的 __init__.py
中,
from bar import a
实际上做的事情类似于
a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)
并定义了一个新变量(如果你愿意,可以称为 bar/__init__.py:a
)。因此,你在 __init__.py
中的 from bar import a
将名称 bar/__init__.py:a
绑定到原始的 bar.py:a
对象(None
)。这就是为什么你可以在 __init__.py
中使用 from bar import a as a2
:在这种情况下,很明显你有 bar/bar.py:a
和一个 不同 的变量名 bar/__init__.py:a2
(在你的例子中,这两个变量的名字恰好都是 a
,但它们仍然存在于不同的命名空间中:在 __init__.py
中,它们是 bar.a
和 a
)。
现在,当你执行
import bar
print bar.a
时,你访问的是变量 bar/__init__.py:a
(因为 import bar
导入了你的 bar/__init__.py
)。这是你修改的变量(变成了 1)。你并没有触碰到变量 bar/bar.py:a
的内容。所以当你接下来执行
bar.foobar()
时,你调用的是 bar/bar.py:foobar()
,它访问的是来自 bar/bar.py
的变量 a
,这个变量仍然是 None
(当 foobar()
被定义时,它一次性绑定了变量名,所以 bar.py
中的 a
是 bar.py:a
,而不是其他模块中定义的任何 a
变量——因为在所有导入的模块中可能会有很多 a
变量)。因此最后输出的就是 None
。
总结:最好避免在 import bar
中出现任何歧义,方法是 不要 有任何 bar/bar.py
模块(因为 bar.__init__.py
已经使目录 bar/
成为一个包,你也可以用 import bar
导入它)。
你在使用 from bar import a
这个语句。这样一来,a
就成了你当前模块中的一个全局符号(或者说是这个导入语句所在的作用域中的符号)。
当你给 a
赋一个新值时,其实只是改变了 a
指向的值,而不是改变它本身的值。你可以试着在 __init__.py
中直接用 import bar
来导入 bar.py
,然后在这里进行实验,比如设置 bar.a = 1
。这样,你实际上是在修改 bar.__dict__['a']
,这才是上下文中 a
的“真实”值。
这个过程有点复杂,因为涉及到三层结构,但 bar.a = 1
其实是在修改名为 bar
的模块中的 a
的值,而这个模块是从 __init__.py
中派生出来的。它并不会改变 foobar
看到的 a
的值,因为 foobar
是在实际的文件 bar.py
中运行的。如果你想改变那个值,可以设置 bar.bar.a
。
使用 from foo import bar
这种导入方式有一个危险:它把 bar
分成了两个符号,一个是在 foo
中全局可见的,最开始指向原始值,另一个是在执行 import
语句的作用域中可见的。改变一个符号指向的地方,并不会改变它原本指向的值。
这种情况在尝试从交互式解释器中 reload
一个模块时会特别麻烦。