Python 如何从另一个模块更改模块变量?

Python 如何从另一个模块更改模块变量?,python,import,module,Python,Import,Module,假设我有一个名为bar的包,它包含bar.py: a = None def foobar(): print a from bar import a, foobar 和\uuuu init\uuuuu.py: a = None def foobar(): print a from bar import a, foobar 然后我执行这个脚本: import bar print bar.a bar.a = 1 print bar.a bar.foobar() 以下是我

假设我有一个名为
bar
的包,它包含
bar.py

a = None

def foobar():
    print a
from bar import a, foobar
\uuuu init\uuuuu.py

a = None

def foobar():
    print a
from bar import a, foobar
然后我执行这个脚本:

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()
以下是我的期望:

None
1
1
以下是我得到的:

None
1
None

有人能解释我的误解吗?

您使用的是bar import a的
a
成为导入模块的全局范围(或导入语句所在的任何范围)中的符号

当您为
a
指定一个新值时,您只是在更改
a
所指的值,而不是实际值。尝试使用
中的
导入bar
直接导入
bar.py
,并通过设置
bar.a=1
在那里进行实验。这样,您实际上将修改
条。
这是
a
在此上下文中的“真实”值

它有点复杂,有三层,但是
bar.a=1
改变了名为
bar
的模块中
a
的值,该模块实际上是从
\uu init\uuuuuuuuupy
派生的。它不会更改
foobar
看到的
a
的值,因为
foobar
存在于实际文件
bar.py
中。如果您想更改,可以设置
bar.bar.a

这是使用
import
语句的
from foo import bar
形式的危险之一:它将
bar
拆分为两个符号,一个从
foo
中全局可见的符号,它开始指向原始值,并在执行
import
语句的范围内显示另一个符号。更改符号指向的位置也不会更改其指向的值


当试图从交互式解释器中重新加载模块时,这类东西是一个杀手。

这个问题的一个困难来源是您有一个名为
bar/bar.py
import bar
导入
bar/\u init\uuuuuuuuupy
bar/bar.py
,具体取决于导入的位置,这使得跟踪哪个
a
bar.a
有点麻烦

以下是它的工作原理:

理解发生了什么的关键是要认识到在
\uuuu init\uuuuuuuuy.py

from bar import a
实际上是这样的

a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)
并定义一个新变量(
bar/\uuuu init\uuuu.py:a
,如果您愿意)。因此,您的
从bar导入
\uuuu init\uuuuu.py
中的
将名称
bar/\uuuuuuu init\uuuuuuuu.py:a
绑定到原始
bar.py:a
对象(
)。这就是为什么您可以在
\uuuu init\uuuuuuuuuupy
中从bar导入a作为a2
的原因:在这种情况下,很明显,您既有
bar/bar.py:a
又有一个不同的变量名
bar/\uuuuuuuu init\uuuuuuuupy:a2
(在您的例子中,这两个变量的名称恰好都是
a
,但它们仍然存在于不同的名称空间中:在
中,它们是
bar.a
a

现在,当你

import bar

print bar.a
您正在访问变量
bar/\uu init\uuuuuuuuuuupy.a
(因为
import bar
导入您的
bar/\uuuuuuuuuuuuuuuuuuupy.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
变量)。因此最后的
输出

结论:最好不要使用任何
bar/bar.py
模块(因为
bar.\uu init\uuuu.py
使目录
bar/
成为一个包,您也可以使用
import bar
导入),从而避免
import bar
中的任何歧义。

换句话说: 事实证明,这种误解很容易产生。 使用对象而不是符号。我建议Python语言参考使这一点更清晰,更少稀疏

来自
表单不绑定模块名称:它通过
标识符列表,在中找到的模块中查找每个标识符
步骤(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”本质上是对实际值的引用:您不是在复制INT,而是在复制引用地址)

事实上,我发现Python的导入方式比Java的要干净得多,因为名称空间/作用域应始终保持适当的分离,并且不会以意外的方式相互干扰。例如:更改名称空间中对象与名称的绑定(读:将某个内容分配给模块的全局属性)绝不会影响Python中的其他名称空间(读取:导入的引用),但会影响Java等。在Python中,您只需要了解导入的内容,而在Java中,您还必须了解其他模块,以防它改变