Search code examples
pythonimportglobal-variables

Global variable imported from a module does not update - why?


I'm having trouble understanding why importing a global variable from another module works as expected when using import, but when using from x import * the global variable doesn't appear to update within its own module

Imagine I have 2 files, one.py:

def change(value):
        global x
        x = value

x = "start"

and two.py:

from one import *
print x
change("updated")
print x

I'd expect:

start
updated

But I get...

start
start

If I import the module normally it works as expected

import one
print one.x
one.change("updated")
print one.x

Result...

start
updated

Given that I can't change ony.py's use of global variables (not my fault), and two.py is meant to be a sort of wrapper* around one.py, I'd really like to avoid using the one. namespace throughout two.py for the sake of one stubborn variable.

If it's not possible a novice-level explantion of what's going on might help me avoid getting stuck like this again. I undertand that one.x is getting updated, but two.x isn't respecting the updates, but I don't know why.


Solution

  • You can think of a package as a dict. Every function and variable in a package is listed as a key in that dict which you can view using globals().

    When you import an object from another package, you copy a reference to the object into your own package under a name (usually the same, different if you import <var> as <name>).

    By setting the value in the other package, you overwrite the key in that package with a new value, but you leave your reference pointing to the old one.

    An analogy with dicts

    We can demonstrate the process using dicts as an analogy:

    # Our 'packages'
    one = {'x': 'start'}
    two = {}
    
    # Equivalent of our import
    two['x'] = one['x']   
    
    # Variable updated in `one'
    one['x'] = 'updated'
    
    # ... and accessed in `two`
    print(two['x'])   # Still 'start'
    

    We would not expect the two dict to be updated just because we overwrote a value in one.

    What you can do about it

    You can modify the object as long as you don't break the pointer by overwriting the variable. For example if x was a dict, you could change a value inside the dict and the two variables would still point to the same object.

    Alternatively you could attach a variables to the function like this:

    def change(value):
        change.x = value
    

    This does the same work by ensuring we are mutating the same object.

    A better answer yet might be to wrap both items in an object if they need to travel together:

    class Changer:
        x = 'start'
    
        @classmethod
        def change(cls, value):
            cls.x = value
    

    At this point however, we could modify the value directly as an attribute:

    Changer.x = 'updated'
    

    Which might be the simplest.