Post

Python Packages Revisited

Python Packages Revisited

Python Packages Revisited

I have always struggled with managing Python packages, especially as I end up with long chains of relative imports like from ...thing import stuff, but it turns out that a Python package always knows the root package name, and can access things from the root package downward via from package.things.thing import stuff

[!NOTE]

  • Trying to run a Python script within the package, without calling it via the package itself will run into errors that mention no known parent package
  • By default you execute a package via python -m package.module_name
  • If the package has a __main__.py then you can use python -m package and this will execute the code in package.__main__

An example directory for a Python package that uses the package.module syntax can be found below. It uses __init__.py files to expose functions from within folders for easier access from package, and lays out the possibilty of a core directory of features that all modules within the package can access with ease

Directory Tree

1
2
3
4
5
6
7
8
9
10
project/
└── package/
    ├── core/
    │   ├── features.py
    │   └── __init__.py
    ├── tools/
    │   ├── things.py
    │   └── __init__.py
    ├── __init__.py
    └── __main__.py

package/

1
2
3
4
5
package/
├── core/
├── tools/
├── __init__.py
└── __main__.py

__init__.py

1
2
3
4
5
6
7
"""Serves as the public API for the package"""
# < =======================================================
# < Imports
# < =======================================================

from .core import feature
from .tools import thing

__main__.py

1
2
3
4
5
6
7
8
9
10
11
12
"""Package entry point, accessed via python -m package"""
# < =======================================================
# < Imports
# < =======================================================

from .tools import thing

# < =======================================================
# < Execution
# < =======================================================

thing()

core/

1
2
3
core/
├── features.py
└── __init__.py

__init__.py

1
2
3
4
5
6
"""Expose features to be accessed via package.core"""
# < =======================================================
# < Imports
# < =======================================================

from .features import feature

features.py

1
2
3
4
5
6
7
8
9
10
"""Features to be exported via package/core/__init__.py"""
# < =======================================================
# < Features to be Exported
# < =======================================================

def feature() -> None:
    """Feature function to print information"""
    print("You have called function 'feature'")
    print("This function is accessible via package.core")
    print("This function is accessible via package.core.features")

tools/

1
2
3
tools/
├── things.py
└── __init__.py

__init__.py

1
2
3
4
5
6
"""Expose features to be accessed via package.tools"""
# < =======================================================
# < Imports
# < =======================================================

from .things import thing

things.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"""Things to be exported via package/tools/__init__.py"""
# < =======================================================
# < Imports
# < =======================================================

from package.core import feature

# < =======================================================
# < Things to be Exported
# < =======================================================

def thing() -> None:
    """Thing function to print information"""
    print("You have called function 'thing'")
    print("This function is accessible via package.tools")
    print("This function is accessible via package.tools.things")
    print("Preparing to call package.core.features.feature()\n")
    feature()
This post is licensed under CC BY 4.0 by the author.