【问题标题】:globals()['variable']=value set in a .py file does not reflect value changes when used in other .py fileglobals()['variable']=在 .py 文件中设置的值不会反映在其他 .py 文件中使用时的值更改
【发布时间】:2019-01-25 06:40:06
【问题描述】:

globalEx1.py

globals()['a']='100'

def setvalue(val):
    globals()['a'] = val

globalEx2.py

from globalEx1 import *

print a
setvalue('200')
print a 

在执行globalEx2.py:

输出:

100
100

如何使用函数更改 globals['a'] 的值,使其反映在 .py 文件中?

【问题讨论】:

    标签: python


    【解决方案1】:

    每个模块都有自己的全局变量。 Python 的行为完全符合预期。更新 globalEx1a 以指向其他内容不会影响 globalEx2a 指向的位置。

    有多种解决方法,具体取决于您想要什么。

    1. setvalue() 调用后重新导入a
    2. return a 并分配它,例如 a = setvalue()
    3. import globalEx1 并使用 globalEx1.a 而不是 a。 (或使用 import globalEx1 as 和更短的名称。)
    4. globalEx2globals() 作为参数传递给setvalue 并设置其值。
    5. a 设为可变对象包含您的值,如列表、字典或types.SimpleNamespace,并在setvalue 中对其进行变异。
    6. setvalue 中使用inspect 从其堆栈帧中获取调用者的全局变量。 (方便,但易碎。)

    最后一个选项看起来适合我.. 它会以最少的代码更改完成工作,但我可以使用相同的方式更新多个模块的全局变量吗?或者它只给我调用者的全局变量?

    选项 6 实际上是最危险的。调用者本身基本上成为函数的隐藏参数,因此像来自另一个模块的装饰器之类的东西可以在没有警告的情况下破坏它。选项 4 只是使隐藏参数显式,所以它不是那么脆弱。

    如果您需要它跨两个以上的模块工作,选项 6 还不够好,因为它只为您提供当前调用堆栈。对于您似乎正在尝试做的事情,选项 3 可能是最可靠的。


    选项 1 是如何工作的?我的意思是再次运行 -> “from globalEx1 import *”,因为我有很多变量,比如 'a'。

    模块在第一次导入时成为对象,并保存在sys.modules 缓存中,因此再次导入它不会再次执行模块。 from ... import(即使是*)只是从该模块对象中获取属性并将它们添加到本地范围(如果在顶层完成,即在任何定义之外,这是模块全局变量。)

    模块对象的__dict__ 基本上是它的全局变量,因此任何改变模块全局变量的函数都会影响生成的模块对象的属性,即使它是在导入模块之后完成的。

    我们不能从 python 函数中执行 from 'globalEx1 import *',有什么替代方法吗?

    星型语法只允许在顶层使用。但请记住,它只是从模块对象中读取属性。所以你可以得到所有模块属性的字典,比如

    return vars(globalEx1)
    

    这将比* 给你更多。默认情况下,它不返回以_ 开头的名称,否则不返回__all__ 中指定的子集。您可以使用字典推导过滤结果字典,甚至.update() 其他模块的全局字典以及结果。

    但与其重新实现此过滤逻辑,不如使用 exec 使其成为顶层。那么你得到的唯一奇怪的键是__builtins__

    namespace = {}
    exec('from globalEx1 import *', namespace)
    del namespace['__builtins__']
    return namespace
    

    然后你可以globals().update(namespace) 或其他任何东西。

    像这样使用exec 可能被认为是不好的形式,但老实说,import * 一开始也是如此。

    【讨论】:

    • 让我试试你的解决方法,感谢您的快速响应
    • 最后一个选项看起来很适合我。它会以最少的代码更改完成这项工作,但我可以使用相同的方式更新多个模块的全局变量吗?或者它只给我调用者的全局变量?
    • 选项 1 是如何工作的?我的意思是再次运行 -> “from globalEx1 import *”,因为我有很多变量,比如 'a'。
    • 完美,导出 globalEx1 再次对我有用.. 我会尝试将它应用于“n”个文件!
    • 我们不能从 python 函数中执行 from 'globalEx1 import *',有什么替代方法吗?
    【解决方案2】:

    这是一个有趣的问题,与字符串不可变这一事实有关。 from globalEx1 import * 行在 globalEx2 模块中创建了两个引用:asetvalueglobalEx2.a 最初指的是与 globalEx1.a 相同的字符串对象,因为这就是导入的工作方式。

    然而,一旦你调用setvalue,它作用于globalEx1 的全局变量,globalEx1.a 引用的值就会被另一个字符串对象替换。由于字符串是不可变的,因此没有办法做到这一点。 globalEx2.a 的值仍然绑定到原始字符串对象,这是应该的。

    您可以在此处找到几种解决方法。最pythonic的是修复globalEx2中的导入:

    import globalEx1
    
    print globalEx1.a
    globalEx1.setvalue('200')
    print globalEx1.a
    

    另一种选择是为a 使用可变容器,然后访问它:

    globals()['a']=['100']
    
    def setvalue(val):
        globals()['a'][0] = val
    
    from globalEx1 import *
    
    print a[0]
    setvalue('200')
    print a[0]
    

    第三种更疯狂的选择是使globalEx2setvalue 成为原始函数的副本,但将其__globals__ 属性设置为globalEx2 的命名空间,而不是globalEx1

    from functools import update_wrapper
    from types import FunctionType
    from globalEx1 import *
    
    _setvalue = FunctionType(setvalue.__code__, globals(), name=setvalue.__name__,
                               argdefs=setvalue.__defaults__,
                               closure=setvalue.__closure__)
    _setvalue = functools.update_wrapper(_setvalue, setvalue)
    _setvalue.__kwdefaults__ = f.__kwdefaults__
    setvalue = _setvalue
    del _setvalue
    
    print a
    ...
    

    你必须复制的原因是__globals__是一个只读属性,而且你不想弄乱globalEx1中的函数。见https://stackoverflow.com/a/13503277/2988730

    【讨论】:

    • 感谢您的回复,阅读两个答案,我理解这是预期的行为。我的场景:“我有很多 .py 文件正在导入单个 .py 文件,其中定义和设置了 globals(),这已经好几年了,直到他们想要一个功能动态更改这些全局值在许多 .py 文件中的多个位置使用。因此考虑添加一个可以全局设置它们而无需修改任何其他 .py 的函数
    • @Waman。确实,您最好的选择是修复导入,但我可以理解为什么这可能不是一个选择。
    • 是的,因为它是遗留代码加上不是我编写的。事实上,我完全知道对单个 .py 文件的全局变量的所有引用。如果重新导入可以像其中一个答案中提到的那样工作,那么我实际上可以触发一个方法,该方法反过来触发其他模块中的重新导入..
    【解决方案3】:

    全局变量只在 import 语句的开头导入一次。因此,如果全局是一个不可变对象,如 str、int 等,则不会反映任何更新。但是,如果全局是一个可变对象,如列表等,则将反映更新。例如,

    globalEx1.py:

    globals()['a']=[100]
    
    def setvalue(val):
        globals()['a'][0] = val
    

    输出将按预期更改:

    [100]
    [200]
    

    一边

    像普通变量一样定义全局变量更容易:

    a = [100]
    
    def setvalue(value):
        a[0] = value
    

    或者在编辑不可变对象的值时:

    a = 100
    
    def setvalue(value):
        global a
        a = value
    

    【讨论】:

      猜你喜欢
      • 2015-09-21
      • 1970-01-01
      • 1970-01-01
      • 2020-08-20
      • 1970-01-01
      • 1970-01-01
      • 2011-04-20
      • 2018-01-26
      • 2023-03-17
      相关资源
      最近更新 更多