Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Skip directory name in import path by importing subpackage in __init__.py

I'm baffled by the importing dynamics in __init__.py. Say I have this structure:

package
├── __init__.py
└── subpackage
    ├── __init__.py
    └── dostuff.py

I would like to import things in dostuff.py. I could do it like this: from package.subpackage.dostuff import thefunction, but I would like to remove the subpackage level in the import statement, so it would look like this:

from package.dostuff import thefunction

I tried putting this in package/__init__.py:

from .subpackage import dostuff

And what I don't understand is this:

# doing this works:
from package import dostuff
dostuff.thefunction()

# but this doesn't work:
from package.dostuff import thefunction
# ModuleNotFoundError: No module named 'package.dostuff'

Why is that, and how can I make from package.dostuff import thefunction work?

like image 265
Florentin Hennecker Avatar asked Apr 18 '19 15:04

Florentin Hennecker


1 Answers

The only way I see to make what you intend would be to actually create a package/dostuff.py module and import all you need in it as from .subpackage.dostuff import thefunction.

The point is that when you use from .subpackage import dostuff in package/__init__.py, you do not rename the original module.

To be more explicit, here is an example of use with both your import and a package/dostuff.py file:

# We import the dostuff link from package
>>> from package import dostuff
>>> dostuff
<module 'package.subpackage.dostuff' from '/tmp/test/package/subpackage/dostuff.py'>

# We use our custom package.dostuff
>>> from package.dostuff import thefunction
>>> package.dostuff
<module 'package.dostuff' from '/tmp/test/package/dostuff.py'>
>>> from package import dostuff
>>> dostuff
<module 'package.dostuff' from '/tmp/test/package/dostuff.py'>

# The loaded function is the same
>>> dostuff.thefunction
<function thefunction at 0x7f95403d2730>
>>> package.dostuff.thefunction
<function thefunction at 0x7f95403d2730>

A clearer way of putting this is:

from X import Y only works when X is an actual module path. Y on the contrary can be any item imported in this module.

This also applies to packages with anything being declared in their __init__.py. Here you declare the module package.subpackage.dostuff in package, hence you can import it and use it.

But if you try to use the module for a direct import, it has to exist on the filesystem

Resources:

  • Python documentation about module management in the import system:
    • https://docs.python.org/3/reference/import.html#submodules.
  • Python import system search behavior:
    • https://docs.python.org/3/reference/import.html#searching
    • https://docs.python.org/3/glossary.html#term-qualified-name
    • https://docs.python.org/2.0/ref/import.html

I hope that makes it clearer

like image 125
aveuiller Avatar answered Sep 20 '22 23:09

aveuiller