Say I have the following consecutive lines in a python code base:
from foo import BAR # succeeds
log.info(f"{dir(BAR)=}") # succeeds
import foo.BAR # succeeds
log.info(f"{dir(foo.BAR)=}") # fails, AttributeError no field BAR in module foo
There's no other code in between. If I deliberately wanted to create this effect, how would I do it? I know it's possible because I'm observing it in a large code base running under Python 3.11, but I have no idea how. What feature of the python import system lets these two forms of import diverge? It seems like the ability to import foo.BAR
must require foo.BAR
to exist, and we even confirm it exists first with from foo import BAR
and logging the fields of BAR
.
foo
is a directory. foo/__init__.py
exists and is empty. foo/BAR.py
exists and contains top level items like functions and classes.
This can happen due to dynamic imports. Typical code to do a dynamic import looks like this:
spec = importlib.util.spec_from_file_location(module_name, file_path)
new_module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = new_module
However, if module_name
has multiple .
separated parts, e.g. foo.bar.buzz
, then this diverges from typical Python import behavior in two ways.
import foo.bar.buzz
python will first import foo
then import foo.bar
then import foo.bar.buzz
. The code above doesn't reproduce this behavior.foo.child
work.So mimicking Python's regular import behavior for foo.bar
requires:
foo
and setting sys.modules["foo"]
.foo.bar
and setting sys.modules["foo.bar"]
.bar
attribute on the foo
module object to point at bar. You can do this with setattr(foo, "bar", bar)
where foo
and bar
are the module objects returned from module_from_spec
.