Waman
Waman

Reputation: 1179

globals()['variable']=value set in a .py file does not reflect value changes when used in other .py file

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

Answers (3)

gilch
gilch

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.

  1. re-import a after the setvalue() call
  2. return a and assign it, like a = setvalue().
  3. import globalEx1 and use globalEx1.a instead of a. (Or use import globalEx1 as and a shorter name.)
  4. pass globalEx2's globals() as an argument to setvalue and set the value on that instead.
  5. make a a mutable object containing your value, like a list, dict or types.SimpleNamespace, and mutate it in setvalue.
  6. use 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

Mad Physicist
Mad Physicist

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

Tim
Tim

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]

Aside

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

Related Questions