Can someone explain this #Python import behavior
-
@bmispelon Then I ran c.py in a VS Codium debugger session, watching A.
- A starts as undefined (everything does).
- After the first line of a.py, A is 1, but I think that VS Codium is actually reporting a.A.
- The import in a is hit, and A goes to undefined. I think VSC is showing b.A.
- b's import runs, and A is 1. I think that's b.A.
- The second line of b is run, and A is 2.
- Execution goes back to c, where the value of A is 2.@bmispelon Here's my VSCodium session:
-
Can someone explain this #Python import behavior?
I'm in a directory with 3 files:a.py contains `A = 1; from b import *`
b.py contains `from a import *; A += 1`
c.py contains `from a import A; print(A)`Can you guess and explain what happens when you run `python c.py`?
@bmispelon Can you share the real-world motivation for this question at some point?
-
@bmispelon Can you share the real-world motivation for this question at some point?
@ehmatthes A very old Django project whose multiple settings files were importing from each other, leaving me very confused for a bit
I definitely would not recommend writing actual code that looks like this!
-
@ehmatthes A very old Django project whose multiple settings files were importing from each other, leaving me very confused for a bit
I definitely would not recommend writing actual code that looks like this!
> whose multiple settings files were importing from each other
You are not the only one who would be confused, please do not mention this in office hours
-
Can someone explain this #Python import behavior?
I'm in a directory with 3 files:a.py contains `A = 1; from b import *`
b.py contains `from a import *; A += 1`
c.py contains `from a import A; print(A)`Can you guess and explain what happens when you run `python c.py`?
Got it! Did it in my head then verified with an interpreter

There's nothing weird here. Python executes stuff sequentially, so:
- in
from a import A
- in a: A = 1
- in a: from b import *
- in b: from a import * (so we have A = 1 in b)
- in b: A += 1 (so we have A = 2 in b)
- in a: finishing previous import, so we now have A = 2 in a
- in
finishing previous import, so we now have A = 2 in C
- in
print(A) -> 2! -
@jonafato Interesting suggestion for a fix! What happens then if all the `from ... import *` are replaced by `from ... import A`?
@bmispelon That would result in the same original behavior, since `__all__` controls the import behavior of `*` but not of individual variables (though I think I have seen projects that allow you to turn that kind of thing into an error via name mangling or some other hack under the hood).
-
Got it! Did it in my head then verified with an interpreter

There's nothing weird here. Python executes stuff sequentially, so:
- in
from a import A
- in a: A = 1
- in a: from b import *
- in b: from a import * (so we have A = 1 in b)
- in b: A += 1 (so we have A = 2 in b)
- in a: finishing previous import, so we now have A = 2 in a
- in
finishing previous import, so we now have A = 2 in C
- in
print(A) -> 2!@bmispelon by the way I'm not sure to understand why the circular import works. I think Python has special handling for some cases where it's able to tell the circular import is "safe" somehow (like "a is almost finished, there's only * to import from b", meaning b can import from a again, and when b is finisehd a is updated again with any symbols declared in b). Tried to find an actual answer in the past but didn't find anything. Maybe should read the sources!
-
@bmispelon by the way I'm not sure to understand why the circular import works. I think Python has special handling for some cases where it's able to tell the circular import is "safe" somehow (like "a is almost finished, there's only * to import from b", meaning b can import from a again, and when b is finisehd a is updated again with any symbols declared in b). Tried to find an actual answer in the past but didn't find anything. Maybe should read the sources!
@bmispelon OK no it's much simpler, module a is simply partially initialized. By the time b imports it, it's not re-executed since it exists in sys.modules, and b imports every existing (yet) symbols within A. from a import A would work too.
-
@treyhunner Tagging you on this since it might qualify as a #Pythonoddity
@bmispelon This is absolutely a Python oddity. I guessed incorrectly. I understand why I guessed incorrectly now that I look back at the code... I'm not sure any Python oddity has stress testeded my mental model of Python's import system as much as this one.
-
Can someone explain this #Python import behavior?
I'm in a directory with 3 files:a.py contains `A = 1; from b import *`
b.py contains `from a import *; A += 1`
c.py contains `from a import A; print(A)`Can you guess and explain what happens when you run `python c.py`?
$ echo 'A = 1; print("A1"); from b import A; print("A2")' > a.py
$ echo 'print("B1"); from a import A; print("B2"); A += 1' > b.py
$ python -c 'from a import A; print(A)'
A1
B1
B2
A2
2I added several prints so that it's possible to tell what order code is executed, and changed
import *toimport Abecause I think it improves clarity without changing the behavior.- The main program runs
- It encounters an import of
aso it starts executing the content ofa.pyin a newly createdamodule - It sets
A.a=1via the assignment statement ina.py - It encounters an import of
bso it starts executing the content ofb.pyin a newly createdbmodule - It sets
b.A=1byfrom...import - It adds 1 to
b.Aso thatb.Ais now equal to 2 - Execution reaches the end of
b.pyso it returns toa.py a.pysetsa.Ato 2 byfrom...import- Execution reaches the end of
a.pyso it returns to the main program. - The main program sets
__main__.Ato 2 byfrom ...import - The value of
Ais printed (2)
-
R relay@relay.an.exchange shared this topic