How do modules and scope interact when the same module is imported into the same environment multiple times?

Viewed 132

Imagine we have modules A, B, and C, and that module C has some variable X at module scope.

A imports B, which (for its own purposes) imports C. A also directly imports C.

If A changes the value of C.X, does that change the value of C.X that B sees? Likewise, if B changes its value of C.X, does that change the value that A sees?

In other words, when a module is imported multiple times into the same interpreter context, do the mutable variables in that module's namespace effectively share memory?

2 Answers

If A changes the value of C.X, does that change the value of C.X that B sees?

tl;dr:

It depends on how you import X, and on X's mutability.

With import C

If both A and B import the C module with a import C statement, they both can access and modify C.X. The name X is bound to the C module, which both A and B have access to. Example:

C.py

X = 1

B.py

import C

def func():
     print(C.X)

A.py

import B
import C

C.X = 2
B.func()

Running A.py prints 2 - A effectively modified the X attribute of the C module's namespace, and this change is reflected in B when calling func().

With from C import X

The from _ import _ statement takes an attribute from an external module and binds it to the current module. When module A imports X from C, it simply binds that value to A's namespace. In other words, changes to X in A's namespace will not reflect on changes to B.X or C.X. Example:

C.py

X = 1

B.py

from C import X

def func():
     print(X)

A.py

import B
from C import X

X = 2
B.func()

Running A.py prints 1, because B.func() will print the value stored at B.X, and we've only modified A.X.

What if X is mutable?

If X is mutable (lists, dictionaries, user-defined classes or instances, etc), then it doesn't really matter how you import it - mutating the imported object in any module will propagate across all modules. Example:

C.py

X = {}  # mutable object (empty dictionary)

B.py

from C import X

def func():
     print(X)

A.py

import B
import C

C.X['key'] = 'value'
B.func()

Running A.py prints {'key': 'value'}. We've effectively modified the same object, even though A used a import _ statement, and B used a from _ import _ statement. This is true for any mutable object, no matter how A or B imports it.

A module is only ever loaded into memory once. sys.modules contains a masterlist of which modules are loaded throughout the entire program, and where they're located.

If multiple files import the same module, it gets loaded into memory once and cached in sys.modules, and then subsequent import attempts just get the cached version.

Therefore, all imports of the same module (regardless of file) point to the same block of memory. So, any memory modified within that module will persist across the entire program.


You can actually test this, by reassigning sys.stdout and sys.stderr to filestreams instead of the output streams, which will modify the print and error output for every print() statement throughout the rest of the program. (to set them back you can use sys.__stdout__ and sys.__stderr__, which can also be similarly redefined but shouldn't). This shows that every part of the program uses the same instance of the sys module

Related