I am using Python 3.9.10
I understand that the question posed is rather verbose and confusing, however I couldn't think of a better way to phrase it, so please allow me to illustrate the problem below:
I have a package, test_import
, with the following structure:
test_import/
├── __init__.py
├── moduleA.py
└── moduleB/
├── __init__.py
└── moduleB.py
test_import/__init__.py
contains the following:
from . import moduleA
from . import moduleB
test_import/moduleA.py
contains the following:
def test_func_module_a(s: str):
print(f"test_import.moduleA.test_func_module_a : {s}")
test_import/moduleB/moduleB.py
contains the following:
def test_func_module_b(s: str):
print(f"test_import.moduleB.moduleB.test_func_module_b: {s}")
And, finally, test_import/moduleB/__init__.py
contains the following:
from .moduleB import test_func_module_b
Lastly, with test_import
in my $PYTHONPATH
, I have the a python file, test_main.py
, which imports and uses the (not-so-useful) test_import
library:
import test_import as ti
ti.moduleA.test_func_module_a("ti.moduleA.test_func_module_a")
ti.moduleB.test_func_module_b("ti.moduleB.test_func_module_b")
ti.moduleB.moduleB.test_func_module_b("ti.moduleB.moduleB.test_func_module_b")
When I run test_main.py
, I expect to see the following:
my.username@myMac:Desktop$ python3 test_main.py
test_import.moduleA.test_func_module_a : ti.moduleA.test_func_module_a
test_import.moduleB.moduleB.test_func_module_b: ti.moduleB.test_func_module_b
Traceback (most recent call last):
File "/Users/my.username/Desktop/test_import_main.py", line 11, in <module>
ti.moduleB.moduleB.test_func_module_b("ti.moduleB.moduleB.test_func_module_b")
AttributeError: module 'test_import.moduleB' has no attribute 'moduleB'
I expect to see this because in test_import/__init__.py
, I have only imported the specific method, test_func_module_b
, into the namespace belonging to test_import.moduleB
. I have not imported the moduleB.py
module in test_import/moduleB/__init__.py
i.e. from . import moduleB
.
However, when I run test_main.py
, I get the following, without any namespace-related AttributeError raised:
my.username@myMac:Desktop$ python3 test_main.py
test_import.moduleA.test_func_module_a : ti.moduleA.test_func_module_a
test_import.moduleB.moduleB.test_func_module_b: ti.moduleB.test_func_module_b
test_import.moduleB.moduleB.test_func_module_b: ti.moduleB.moduleB.test_func_module_b
i.e. no AttributeError related to the test_import.moduleB
namespace.
This is confusing because (as I said above), I did not import moduleB.py
into test_import.moduleB
's namespace.
If I leave test_import/__init__.py
as an empty file, and run test_main.py
, AttributeError
s are raised for both of the following lines, related to the absence of either in the subpackage's (test_import.moduleB
) namespace, i.e.:
my.username@myMac:Desktop$ python3 test_import_main.py
test_import.moduleA.test_func_module_a : ti.moduleA.test_func_module_a
Traceback (most recent call last):
File "/Users/my.username/Desktop/test_import_main.py", line 10, in <module>
ti.moduleB.test_func_module_b("ti.moduleB.test_func_module_b")
AttributeError: module 'test_import.moduleB' has no attribute 'test_func_module_b'
and
my.username@myMac:Desktop$ python3 test_main.py
test_import.moduleA.test_func_module_a : ti.moduleA.test_func_module_a
Traceback (most recent call last):
File "/Users/my.username/Desktop/test_import_main.py", line 11, in <module>
ti.moduleB.moduleB.test_func_module_b("ti.moduleB.moduleB.test_func_module_b")
AttributeError: module 'test_import.moduleB' has no attribute 'moduleB'
Could anyone shed some light on how Python's import system has achieved this?
PS - As there have been so many import-related questions on StackOverflow, I expect there to be similar questions. However, I have been unable to find answers to this particular issue.
CodePudding user response:
When Python encounters a submodule as part of an import (the y
in from x.y import z
(absolute) or from .y import z
(relative)), it will place a reference to the submodule in the parent module's namespace. This is outlined in the docs:
When a submodule is loaded using any mechanism [...] a binding is placed in the parent module’s namespace to the submodule object.
[...]
The invariant holding is that if you havesys.modules['spam']
andsys.modules['spam.foo']
[...] the latter must appear as thefoo
attribute of the former.