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

3

14

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.

Veil answered 4/3, 2020 at 11:1 Comment(2)
You code at the end import two; print x doesn't make sense. I assume you meant import two; print two.x; two.change.... If you currently have from one import x in any other files those files are already broken for the same reasons.Reticulum
Thank you Alex, you are right. To avoid confusion I've deleted that final block while I work out a better explanation.Veil
P
12

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.

Physicalism answered 4/3, 2020 at 11:30 Comment(1)
Excellent, you've even covered what my real-life version of the problem is. My real x is a dict, but instead of mutating it in the change function, I'm starting over with a new dict.Veil
L
1

You can import twice from the same module like this:

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

basically by attaching module name you are forcing the code to use the imported module's variable.

Laquanda answered 17/12, 2021 at 16:35 Comment(0)
R
0

This is probably the best you can do inside two.py:

import one
from one import x

def change(value):
    one.change(value)
    global x
    from one import x
Reticulum answered 4/3, 2020 at 11:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.