globals()['variable']=在.py文件中设置的值在其他.py文件中使用时不反映值更改

2024-10-03 09:10:08 发布

您现在位置:Python中文网/ 问答频道 /正文

全局1.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文件中?你知道吗


Tags: 文件函数frompyimportdefval全局
3条回答

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

但是,一旦调用setvalue,它对globalEx1的全局变量进行操作,globalEx1.a引用的值就会被另一个string对象替换。由于字符串是不可变的,因此无法在适当的位置执行此操作。globalEx2.a的值仍然绑定到原始string对象,正如它应该绑定的那样。你知道吗

你这里有一些解决办法。最具python风格的是在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。你知道吗

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

全局1.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

每个模块都有自己的全局变量。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成为一个包含您的值的可变对象,如list、dict或types.SimpleNamespace,并在setvalue中对其进行变异。你知道吗
  6. setvalue内使用inspect从调用方的堆栈帧获取调用方的全局变量。(方便,但易碎。)

Last option looks suitable for me.. it will do the job with minimal code change but can I update globals of multiple modules using same way? or it only gives me the caller's globals?

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

如果您需要在两个以上的模块上使用它,那么选项6就不够好,因为它只提供当前的调用堆栈。选项3可能是最可靠的你似乎要做的事。你知道吗


How does option 1 work? I mean is it about running again -> "from globalEx1 import *" because I have many variables like 'a'.

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

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

We cannot do from 'globalEx1 import *' from a python function, any alternative to this?

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

return vars(globalEx1)

这会给你比*更多的东西。默认情况下,它不返回以_开头的名称,否则不返回在__all__中指定的子集。您可以使用dict comprehension过滤结果dict,甚至.update()使用结果过滤其他模块的全局dict。你知道吗

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

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

然后你就可以globals().update(namespace)或者别的什么了。你知道吗

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

相关问题 更多 >