pythonimportpathstatic-variables

Python Importing Same Object via Different Paths - Different Behaviour Between Class Attribute and Primitive


Python 3.10.6

Suppose you have the following files:

main.py
setter.py
Folder/shared.py

With these contents:

# Folder/shared.py
class Shared:
    i = 0 # something that is used in multiple locations
# setter.py
from Folder.shared import Shared
def set_i():
    global Shared
    print("mem in setter: " + hex(id(Shared.i)))
    Shared.i = 2 # set the shared resource
# main.py
IMPORT_VIA_PATH = False
if IMPORT_VIA_PATH:
    import sys, os
    pwd = os.path.dirname(os.path.abspath(__file__))
    shared_path = pwd + '/Folder'
    sys.path.insert(0, shared_path)
    from shared import Shared
else:
    from Folder.shared import Shared
from setter import set_i
set_i()
print(Shared.i)
print("mem in main: " + hex(id(Shared.i)))

If IMPORT_VIA_PATH is False, main prints 2 for i's value, and the memory addresses are different.
If IMPORT_VIA_PATH is True, main prints 0 for i's value, and the memory addresses are the same.


Even more confusingly, if Shared.i is replaced with just i, throughout:

# Folder/shared.py
i = 0 # something that is used in multiple locations
# setter.py
from Folder.shared import i
def set_i():
    global i
    print("mem in setter: " + hex(id(i)))
    i = 2 # set the shared resource
# main.py
IMPORT_VIA_PATH = True
if IMPORT_VIA_PATH:
    import sys, os
    pwd = os.path.dirname(os.path.abspath(__file__))
    shared_path = pwd + '/Folder'
    sys.path.insert(0, shared_path)
    from shared import i
else:
    from Folder.shared import i
from setter import set_i
set_i()
print(i)
print("mem in main: " + hex(id(i)))

Then for both IMPORT_VIA_PATH True or False, i remains zero and the memory addresses are the same. What is going on here?


Solution

  • Because if you import from a different path, i.e. your import statement uses a different module name, then python treats it as a different module. In one case, sys.modules will contain "Folder.shared", so when you import shared, it will check if "shared" is in sys.modules, not find it, so it doesn't just retrieve the already imported module, and then it imports it as a new module.

    There is almost certainly no good reason to be doing this to begin with. So you should just not do it.

    There are no "primitives" in Python. However, CPython has an implementation detail that small integers are cached, so that is why the same object is re-used for the integer 0.

    Also, when you do from module import i, you are assigning the value of module.i to a new variable in the importing module.

    So of course i doesn't change. It's a different variable (possibly referring to the same object)

    And calling set_i only affects module.i, not the i that you have in your main module.