假设我有一个名为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
任何人都能解释我的错误观念吗?
您正在使用from bar import a
。 a
成为导入模块的全局范围中的符号(或导入语句出现的任何范围)。
当您为a
分配新值时,您只是更改a
指向的值,而不是实际值。尝试直接用bar.py
中的import bar
导入__init__.py
并通过设置bar.a = 1
在那里进行实验。这样,您实际上将修改bar.__dict__['a']
,这是a
在此上下文中的“真实”值。
这有点令人费解,有三层,但bar.a = 1
在a
模块中更改了bar
的值,该模块实际上是从__init__.py
派生出来的。它没有改变a
看到的foobar
的值,因为foobar
生活在实际文件bar.py
中。如果你想改变它,你可以设置bar.bar.a
。
这是使用from foo import bar
形式的import
形式的危险之一:它将bar
分成两个符号,一个从foo
内部全局可见,它开始指向原始值,并且在import
语句的范围内可见的不同符号执行。更改符号指向的位置不会更改它指向的值。
当从交互式解释器尝试reload
模块时,这种东西是一个杀手。
这个问题的一个难点是你有一个名为bar/bar.py
的程序:import bar
进口bar/__init__.py
或bar/bar.py
,取决于它在哪里完成,这使得跟踪哪个a
是bar.a
有点麻烦。
下面是它的工作原理:
理解发生的事情的关键是要在你的__init__.py
中意识到
from bar import a
实际上做了类似的事情
a = bar.a # … with bar = bar/bar.py (as if bar were imported locally from __init__.py)
并定义一个新变量(bar/__init__.py:a
,如果你愿意)。因此,你在from bar import a
的__init__.py
将名称bar/__init__.py:a
绑定到原始的bar.py:a
对象(None
)。这就是为什么你可以在from bar import a as a2
做__init__.py
:在这种情况下,很明显你有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()
,它从a
访问变量bar/bar.py
,它仍然是None
(当定义foobar()
时,它一劳永逸地绑定变量名称,所以a
中的bar.py
是bar.py:a
,而不是在另一个模块中定义的任何其他a
变量 - 因为那里可能是所有导入模块中的许多a
变量)。因此最后的None
输出。
换句话说:结果证明这种误解很容易。 It is sneakily defined in the Python language reference:使用对象而不是符号。我建议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”本质上是对实际值的引用:你没有复制INT,你正在复制引用地址)