Home > database >  Why does importing a specific subpackage module's method in the subpackage's __init__.py p
Why does importing a specific subpackage module's method in the subpackage's __init__.py p

Time:04-05

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, AttributeErrors 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 have sys.modules['spam'] and sys.modules['spam.foo'] [...] the latter must appear as the foo attribute of the former.

  • Related