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


106

假设我有一个名为的软件包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

谁能解释我的误解?

Answers:


103

您正在使用from bar import aa在导入模块的全局范围(或​​发生import语句的任何范围)中成为符号。

当您为指定新值时a,您也只是在更改哪些值a,而不是实际值。尝试bar.py直接使用import barin 导入,__init__.py并通过设置在那里进行实验bar.a = 1。这样,您实际上将在此上下文中修改bar.__dict__['a']哪个是“实际”值a

它有点复杂,分为三层,但是bar.a = 1更改了实际上是从派生a的模块中的值。它不会更改所看到的值,因为它存在于实际文件中。您可以设置是否要更改它。bar__init__.pyafoobarfoobarbar.pybar.bar.a

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

尝试reload从交互式解释器访问模块时,这种东西是致命的。


谢谢!您的回答可能使我节省了寻找罪魁祸首的几个小时。
jnns

看起来,即使这种bar.bar.a方法在大多数用例中也无济于事。混合编译或模块加载以及运行时分配可能不是一个好主意,因为您最终会使自己(或其他使用您代码的人)感到困惑。尤其是在行为上有些不一致的语言中,就像python一样。
matanster

26

这个问题困难的一个原因是,你有一个名为程序bar/bar.pyimport bar可导入bar/__init__.pybar/bar.py,取决于它完成,这使得它有点笨重跟踪哪些abar.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,如果需要的话)。因此,您的from bar import ain __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.aa)。

现在,当你做

import bar

print bar.a

您正在访问变量bar/__init__.py:a(因为import bar导入了your bar/__init__.py)。这是您修改的变量(为1)。您没有触及variable的内容bar/bar.py:a。所以当你随后做

bar.foobar()

您调用bar/bar.py:foobar(),它将访问a来自的变量bar/bar.py,后者仍然是Nonefoobar()定义后,它将一劳永逸地绑定变量名称,因此ain bar.pybar.py:a,而不是a另一个模块中定义的任何其他变量-因为a在所有导入的模块中可能有很多变量)。因此是最后一个None输出。

结论:最好是避免任何含糊之处import bar,由具有任何bar/bar.py模块(因为bar.__init__.py品牌目录bar/包已经,您还可以导入import bar)。


12

换一种说法:事实证明,这种误解很容易造成。 它是在Python语言参考中偷偷地定义的:使用object而不是symbol。我建议使用Python语言参考使这一点更清晰,更稀疏。

from形式不结合模块名称:它通过标识符的列表,看起来它们中的每一个向上的模块中,在步骤(1)中发现,并结合在本地名称空间中的名称对象从而找到。

然而:

导入时,将导入已导入符号的当前值,并将其添加到已定义的命名空间中。 您不是在导入参考,而是在有效导入值。

因此,要获取的更新值i,必须导入一个变量,该变量包含对该符号的引用。

换句话说,导入import与JAVA中的,externalC / C ++中的声明甚use至PERL中的子句都不一样。

而是在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的方式import比Java更清洁,因为名称空间/作用域应始终保持适当的分隔,并且永远不会以意想不到的方式相互干扰。如此处所示:更改对象与名称空间中名称的绑定(请参阅:将某些内容关联到模块的全局属性)不会影响Python中的其他名称空间(请参阅:已导入的引用)。但这在Java等中也是如此。在Python中,您只需要了解导入的内容,而在Java中,您还必须了解其他模块,以防以后更改此导入的值。
蒂诺

2
我必须非常不同意。导入/包含/使用使用参考表格的历史由来已久,而不是几乎所有其他语言的价值形式。
Mark Gerolimatos

4
该死,我打了退货……这是通过引用导入的期望(按照@OP)。实际上,无论经验如何,都必须以一种“警惕”的方式告诉新的Python程序员。那永远都不会发生:基于常见用法,Python采取了错误的方法。如果需要的话,创建一个“导入值”,但是不要在导入时将符号与值混淆。
Mark Gerolimatos
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.