Reputation: 1179
globalEx1.py:
globals()['a']='100'
def setvalue(val):
globals()['a'] = val
globalEx2.py:
from globalEx1 import *
print a
setvalue('200')
print a
On executing globalEx2.py
:
Output:
100
100
How can I change value of globals['a']
using a function, so that it reflects across the .py files?
Upvotes: 1
Views: 121
Reputation: 11681
Each module has its own globals. Python is behaving exactly as expected. Updating globalEx1
's a
to point to something else isn't going to affect where globalEx2
's a
is pointing.
There are various ways around this, depending on exactly what you want.
a
after the setvalue()
callreturn a
and assign it, like a = setvalue()
.import globalEx1
and use globalEx1.a
instead of a
. (Or use import globalEx1 as
and a shorter name.)globalEx2
's globals()
as an argument to setvalue
and set the value on that instead.a
a mutable object containing your value, like a list, dict or types.SimpleNamespace
, and mutate it in setvalue
.inspect
inside setvalue
to get the caller's globals from its stack frame. (Convenient, but brittle.)
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?
Option 6 is actually the riskiest. The caller itself basically becomes a hidden parameter to the function, so something like a decorator from another module can break it without warning. Option 4 just makes that hidden parameter explicit, so it's not so brittle.
If you need this to work across more than two modules, option 6 isn't good enough, since it only gives you the current call stack. Option 3 is probably the most reliable for what you seem to be trying to do.
How does option 1 work? I mean is it about running again -> "from globalEx1 import *" because I have many variables like 'a'.
A module becomes an object when imported the first time and it's saved in the sys.modules
cache, so importing it again doesn't execute the module again. A from ... import
(even with the *
) just gets attributes from that module object and adds them to the local scope (which is the module globals if done at the top level, that is, outside of any definition.)
The module object's __dict__
is basically its globals, so any function that alters the module's globals will affect the resulting module object's attrs, even if it's done after the module was imported.
We cannot do from 'globalEx1 import *' from a python function, any alternative to this?
The star syntax is only allowed at the top level. But remember that it's just reading attributes from the module object. So you can get a dict of all the module attributes like
return vars(globalEx1)
This will give you more than *
would. It doesn't return names that begin with an _
by default, or the subset specified in __all__
otherwise. You can filter the resulting dict with a dict comprehension, and even .update()
the globals dict for some other module with the result.
But rather than re-implementing this filtering logic, you could just use exec to make it the top level. Then the only weird key you'd get is __builtins__
namespace = {}
exec('from globalEx1 import *', namespace)
del namespace['__builtins__']
return namespace
Then you can globals().update(namespace)
or whatever.
Using exec
like this is probably considered bad form, but then so is import *
to begin with, honestly.
Upvotes: 4
Reputation: 114518
This is an interesting problem, related to the fact that strings are immutable. The line from globalEx1 import *
creates two references in the globalEx2
module: a
and setvalue
. globalEx2.a
initially refers to the same string object as globalEx1.a
, since that's how imports work.
However, once you call setvalue
, which operates on the globals of globalEx1
, the value referenced by globalEx1.a
is replaced by another string object. Since strings are immutable, there is no way to do this in place. The value of globalEx2.a
remains bound to the original string object, as it should.
You have a couple of workarounds available here. The most pythonic is to fix the import in globalEx2
:
import globalEx1
print globalEx1.a
globalEx1.setvalue('200')
print globalEx1.a
Another option would be to use a mutable container for a
, and access that:
globals()['a']=['100']
def setvalue(val):
globals()['a'][0] = val
from globalEx1 import *
print a[0]
setvalue('200')
print a[0]
A third, and wilder option, is to make globalEx2
's setvalue
a copy of the original function, but with its __globals__
attribute set to the namespace of globalEx2
instead of 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
...
The reason you have to make the copy is that __globals__
is a read-only attribute, and also you don't want to mess with the function in globalEx1
. See https://stackoverflow.com/a/13503277/2988730.
Upvotes: 1
Reputation: 3427
Globals are imported only once at the beginning with the import statement. Thus, if the global is an immutable object like str, int, etc, any update will not be reflected. However, if the global is a mutable object like list, etc, updates will be reflected. For example,
globalEx1.py:
globals()['a']=[100]
def setvalue(val):
globals()['a'][0] = val
The output will be changed as expected:
[100]
[200]
It's easier to define globals like normal variables:
a = [100]
def setvalue(value):
a[0] = value
Or when editing value of immutable objects:
a = 100
def setvalue(value):
global a
a = value
Upvotes: 0