【问题标题】:How to change a module variable from another module?如何从另一个模块更改模块变量?
【发布时间】:2011-04-01 23:39:30
【问题描述】:

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

谁能解释我的误解?

【问题讨论】:

    标签: python import module


    【解决方案1】:

    这个问题的一个困难来源是您有一个名为 bar/bar.py 的程序:import 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,如果你愿意的话)。因此,__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.aa)。

    现在,当你这样做时

    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()被定义时,它会一劳永逸地绑定变量名,所以@中的a 987654356@ 是bar.py:a,而不是在另一个模块中定义的任何其他a 变量——因为在所有导入的模块中可能有许多a 变量)。因此最后一个None 输出。

    结论:最好避免import bar 中的任何歧义,有任何bar/bar.py 模块(因为bar.__init__.py 已经使目录bar/ 成为一个包,你也可以使用import bar 导入)。

    【讨论】:

      【解决方案2】:

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

      当您为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 形式的import 语句的危险之一:它将bar 分成两个符号,一个在foo 中全局可见,它开始指向原始值和一个在执行import 语句的范围内可见不同的符号。更改符号指向的位置也不会更改它所指向的值。

      当试图从交互式解释器中 reload 一个模块时,这种东西是一个杀手。

      【讨论】:

      • 谢谢!你的回答可能让我节省了几个小时寻找罪魁祸首。
      • 看起来即使是bar.bar.a 方法在大多数用例中也无济于事。混合编译或模块加载和运行时分配可能是一个弱点子,因为您最终会混淆自己(或使用您的代码的其他人)。尤其是在行为有些不一致的语言中,比如 python 可以说是这样。
      【解决方案3】:

      换一种说法: 事实证明,这种误解很容易产生。 It is sneakily defined in the Python language reference: 使用 object 而不是 symbol。我建议 Python 语言参考使这更清晰,更少稀疏..

      from 表单不绑定模块名称:它通过 标识符列表,在找到的模块中查找它们中的每一个 步骤(1),并将本地命名空间中的名称绑定到 object 从而 找到了。

      但是:

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

      因此,要获取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 的 import 方式比 Java 的方式干净得多,因为命名空间/范围应始终保持适当的分离,并且不会以意想不到的方式相互干扰。就像这里:改变对象绑定到命名空间中的名称(阅读:将某些东西分配给模块的全局属性)永远不会影响 Python 中的其他命名空间(阅读:导入的引用)。但是在 Java 等中是这样。在 Python 中,您只需要了解导入的内容,而在 Java 中,您还必须了解其他模块,以防它稍后更改此导入的值。
      • 我不得不非常不同意。导入/包含/使用在所有其他语言中都使用引用形式而不是值形式的悠久历史。
      • 该死的,我按回车键......这是通过引用导入的期望(根据@OP)。事实上,一个新的 Python 程序员,无论多么有经验,都必须以“小心”的方式被告知这一点。这绝不应该发生:根据常见用法,Python 走错了路。如果需要,创建一个“导入值”,但不要在导入时将符号与值混为一谈。
      猜你喜欢
      • 1970-01-01
      • 2016-04-08
      • 1970-01-01
      • 1970-01-01
      • 2016-05-15
      • 1970-01-01
      • 2020-12-30
      相关资源
      最近更新 更多