Is the behaviour of circular imports well-specified in Python, or is it just implementation-specific?
For example in cpython, importing a submodule causes the submodule to be assigned as an attribute of the parent module, but not until after the submodule finishes executing. Is this stated anywhere in the docs or PEPs?
More often then not, python circular imports just work (assisted by conventions like not executing any functions during import, and laying out the source code to define functions in the reverse of expected call order), but a consequence of the above behaviour is that you cannot refer by parent to a partially imported module. For example, if importing package.submodule
causes an import of othermodule
, then othermodule
cannot do import package.submodule as submod
but can do from package import submodule as submod
. Is this explained/specified by the documentation? If not, is it likely to change?
In this case, the discrepancy is because import package.submodule as submod
is implemented as a call to __import__
(that returns package
) followed by an attribute lookup (to retrieve submodule
so that it can be assigned to submod
). In contrast, from package import submodule as submod
causes __import__
to directly return submodule
(which seems to work even if submodule
is still only half-complete). You could figure some of this out if you delve through the cpython bytecode (documented in the dis
section of the standard library) and study the __import__
semantics. However, since resolving circular imports is a common issue in python, it would be useful to find an official high-level summary of what to expect?
Example:
mkdir package
touch package/__init__.py
cat > package/submodule.py << EOF
def f():
print('OK')
import othermodule
othermodule.g()
def notyetdefined():
pass
EOF
cat > othermodule.py << EOF
def g():
try:
import package.submodule as submod # this fails
except AttributeError:
print('FAIL')
from package import submodule as submod # this works ok
submod.f()
#submod.notyetdefined() # this cannot be invoked
EOF
python -c 'import package.submodule'
Output in python 3.6.7:
FAIL
OK