Sitemap
Towards Dev

A publication for sharing projects, ideas, codes, and new theories.

Python Namespace Packages

10 min readDec 28, 2024

--

In Python we have packages to organize the modules.

Press enter or click to view image in full size
Python Packages

Two types of packages:

  1. Regular Package
  2. Namespace Package

Regular Package

A regular package is a directory that contains an __init__.py file. This file indicates that the directory should be treated as a package. The __init__.py file can be empty, but it is often used to initialize the package, define what is exposed to the outside, or run setup code when the package is imported.

mypackage/
__init__.py
module1.py
module2.py

Namespace Package

A namespace package is a package-like structure that does not require an __init__.py file and can span multiple directories. It allows for distributed and flexible package creation, enabling different parts of the package to be split across various locations.

Example:

Imagine the namespace package mynamespace distributed across two directories:

Directory 1:

project1/mynamespace/
module1.py

Directory 2:

project2/mynamespace/
module2.py

Usage

import mynamespace.module1
import mynamespace.module2

Python will combine the two mynamespace directories into a single namespace package, making all modules accessible.

Comparison

Press enter or click to view image in full size

Namespace package example

We learnt that source code of the Namespace package can be spread across multiple directories. It helps the large project to be de-coupled based on the teams so that the components of the packages can be developed and distributed independently.

Ex: One repository( one team) can work on one component, the other team can work on another component of the same package but developed and distributed independently.

Lets learn how the Namespace package helps in developing and distributing the package independently.

Get Logesh’s stories in your inbox

Join Medium for free to get updates from this writer.

Assume we have one corporation(log-corp) that provides one big useful package. But that package’s subpackage/modules are diversified across different repos(directories) as different teams are working on it.

  • log_corp is the main package which has many sub-packages and modules that are developed across the teams and distributed independently.
  • The Versioning and package distribution will happen independently.
  • The only thing they share is the namespace. (All the sub-packages and modules will come under the main package log_corp)

Team 1(Repo 1)

Using the src layout.

LOG_CORP_COMP1/
├── src/
│ └── log_corp/
│ ├── sub_pkg1/
│ │ └── sub_module1.py
│ └── module1.py
├── pyproject.toml
└── README.md

module1.py

def func1():
print('From log-corp module1')

sub_module1.py

def sub_func1():
print('From log-corp submodule1')

pyproject.toml

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "log-corp1" # as if it appears in pypi
authors = [
{name = "Logesh", email = "log@gmail.com"},
]
description = "My package description"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT License"}
classifiers = [
"Programming Language :: Python :: 3",
]
version = "1.0.1" # version 1.0.1

Build the package

PS C:\Users\L\Desktop\log_corp_comp1> ls


Directory: C:\Users\L\Desktop\log_corp_comp1


Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 12/28/2024 11:32 AM src
-a---- 12/28/2024 11:41 AM 387 pyproject.toml
-a---- 12/28/2024 11:31 AM 34 README.md


PS C:\Users\L\Desktop\log_corp_comp1> python -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools
* Getting build dependencies for sdist...
running egg_info
creating src\log_corp1.egg-info
writing src\log_corp1.egg-info\PKG-INFO
writing dependency_links to src\log_corp1.egg-info\dependency_links.txt
writing top-level names to src\log_corp1.egg-info\top_level.txt
writing manifest file 'src\log_corp1.egg-info\SOURCES.txt'
reading manifest file 'src\log_corp1.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp1.egg-info\SOURCES.txt'
* Building sdist...
running sdist
running egg_info
writing src\log_corp1.egg-info\PKG-INFO
writing dependency_links to src\log_corp1.egg-info\dependency_links.txt
writing top-level names to src\log_corp1.egg-info\top_level.txt
reading manifest file 'src\log_corp1.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp1.egg-info\SOURCES.txt'
running check
creating log_corp1-1.0.1
creating log_corp1-1.0.1\src\log_corp
creating log_corp1-1.0.1\src\log_corp1.egg-info
creating log_corp1-1.0.1\src\log_corp\sub_pkg1
copying files to log_corp1-1.0.1...
copying README.md -> log_corp1-1.0.1
copying pyproject.toml -> log_corp1-1.0.1
copying src\log_corp\module1.py -> log_corp1-1.0.1\src\log_corp
copying src\log_corp1.egg-info\PKG-INFO -> log_corp1-1.0.1\src\log_corp1.egg-info
copying src\log_corp1.egg-info\SOURCES.txt -> log_corp1-1.0.1\src\log_corp1.egg-info
copying src\log_corp1.egg-info\dependency_links.txt -> log_corp1-1.0.1\src\log_corp1.egg-info
copying src\log_corp1.egg-info\top_level.txt -> log_corp1-1.0.1\src\log_corp1.egg-info
copying src\log_corp\sub_pkg1\sub_module1.py -> log_corp1-1.0.1\src\log_corp\sub_pkg1
copying src\log_corp1.egg-info\SOURCES.txt -> log_corp1-1.0.1\src\log_corp1.egg-info
Writing log_corp1-1.0.1\setup.cfg
Creating tar archive
removing 'log_corp1-1.0.1' (and everything under it)
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools
* Getting build dependencies for wheel...
running egg_info
writing src\log_corp1.egg-info\PKG-INFO
writing dependency_links to src\log_corp1.egg-info\dependency_links.txt
writing top-level names to src\log_corp1.egg-info\top_level.txt
reading manifest file 'src\log_corp1.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp1.egg-info\SOURCES.txt'
* Building wheel...
running bdist_wheel
running build
running build_py
creating build\lib\log_corp
copying src\log_corp\module1.py -> build\lib\log_corp
creating build\lib\log_corp\sub_pkg1
copying src\log_corp\sub_pkg1\sub_module1.py -> build\lib\log_corp\sub_pkg1
running egg_info
writing src\log_corp1.egg-info\PKG-INFO
writing dependency_links to src\log_corp1.egg-info\dependency_links.txt
writing top-level names to src\log_corp1.egg-info\top_level.txt
reading manifest file 'src\log_corp1.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp1.egg-info\SOURCES.txt'
installing to build\bdist.win-amd64\wheel
running install
running install_lib
creating build\bdist.win-amd64\wheel
creating build\bdist.win-amd64\wheel\log_corp
copying build\lib\log_corp\module1.py -> build\bdist.win-amd64\wheel\.\log_corp
creating build\bdist.win-amd64\wheel\log_corp\sub_pkg1
copying build\lib\log_corp\sub_pkg1\sub_module1.py -> build\bdist.win-amd64\wheel\.\log_corp\sub_pkg1
running install_egg_info
Copying src\log_corp1.egg-info to build\bdist.win-amd64\wheel\.\log_corp1-1.0.1-py3.11.egg-info
running install_scripts
creating build\bdist.win-amd64\wheel\log_corp1-1.0.1.dist-info\WHEEL
creating 'C:\Users\L\Desktop\log_corp_comp1\dist\.tmp-ryh5ya6f\log_corp1-1.0.1-py3-none-any.whl' and adding 'build\bdist.win-amd64\wheel' to it
adding 'log_corp/module1.py'
adding 'log_corp/sub_pkg1/sub_module1.py'
adding 'log_corp1-1.0.1.dist-info/METADATA'
adding 'log_corp1-1.0.1.dist-info/WHEEL'
adding 'log_corp1-1.0.1.dist-info/top_level.txt'
adding 'log_corp1-1.0.1.dist-info/RECORD'
removing build\bdist.win-amd64\wheel
Successfully built log_corp1-1.0.1.tar.gz and log_corp1-1.0.1-py3-none-any.whl
PS C:\Users\L\Desktop\log_corp_comp1>

Team 2(Repo 2)

src layout

LOG_CORP_COMP2/
├── src/
│ └── log_corp/
│ ├── sub_pkg2/
│ │ └── sub_module2.py
│ └── module2.py
├── pyproject.toml
└── README.md

module2.py

def func2():
print('From log-corp module2')

sub_module2.py

def sub_func2():
print('From log-corp submodule2')

pyproject.toml

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "log-corp2" # Different package name from the previous one
authors = [
{name = "Logesh", email = "log@gmail.com"},
]
description = "My package description"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT License"}
classifiers = [
"Programming Language :: Python :: 3",
]
version = "1.0.2" # Different version

Built the package

PS E:\Documents\log_corp_comp2> ls


Directory: E:\Documents\log_corp_comp2


Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 12/28/2024 11:25 AM src
-a---- 12/28/2024 11:31 AM 34 README.md
-a---- 12/28/2024 11:41 AM 387 pyproject.toml


PS E:\Documents\log_corp_comp2> python -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools
* Getting build dependencies for sdist...
running egg_info
creating src\log_corp2.egg-info
writing src\log_corp2.egg-info\PKG-INFO
writing dependency_links to src\log_corp2.egg-info\dependency_links.txt
writing top-level names to src\log_corp2.egg-info\top_level.txt
writing manifest file 'src\log_corp2.egg-info\SOURCES.txt'
reading manifest file 'src\log_corp2.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp2.egg-info\SOURCES.txt'
* Building sdist...
running sdist
running egg_info
writing src\log_corp2.egg-info\PKG-INFO
writing dependency_links to src\log_corp2.egg-info\dependency_links.txt
writing top-level names to src\log_corp2.egg-info\top_level.txt
reading manifest file 'src\log_corp2.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp2.egg-info\SOURCES.txt'
running check
creating log_corp2-1.0.2
creating log_corp2-1.0.2\src\log_corp
creating log_corp2-1.0.2\src\log_corp2.egg-info
creating log_corp2-1.0.2\src\log_corp\sub_pkg2
copying files to log_corp2-1.0.2...
copying README.md -> log_corp2-1.0.2
copying pyproject.toml -> log_corp2-1.0.2
copying src\log_corp\module2.py -> log_corp2-1.0.2\src\log_corp
copying src\log_corp2.egg-info\PKG-INFO -> log_corp2-1.0.2\src\log_corp2.egg-info
copying src\log_corp2.egg-info\SOURCES.txt -> log_corp2-1.0.2\src\log_corp2.egg-info
copying src\log_corp2.egg-info\dependency_links.txt -> log_corp2-1.0.2\src\log_corp2.egg-info
copying src\log_corp2.egg-info\top_level.txt -> log_corp2-1.0.2\src\log_corp2.egg-info
copying src\log_corp\sub_pkg2\sub_module2.py -> log_corp2-1.0.2\src\log_corp\sub_pkg2
Writing log_corp2-1.0.2\setup.cfg
Creating tar archive
removing 'log_corp2-1.0.2' (and everything under it)
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools
* Getting build dependencies for wheel...
running egg_info
writing src\log_corp2.egg-info\PKG-INFO
writing dependency_links to src\log_corp2.egg-info\dependency_links.txt
writing top-level names to src\log_corp2.egg-info\top_level.txt
reading manifest file 'src\log_corp2.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp2.egg-info\SOURCES.txt'
* Building wheel...
running bdist_wheel
running build
running build_py
creating build\lib\log_corp
copying src\log_corp\module2.py -> build\lib\log_corp
creating build\lib\log_corp\sub_pkg2
copying src\log_corp\sub_pkg2\sub_module2.py -> build\lib\log_corp\sub_pkg2
running egg_info
writing src\log_corp2.egg-info\PKG-INFO
writing dependency_links to src\log_corp2.egg-info\dependency_links.txt
writing top-level names to src\log_corp2.egg-info\top_level.txt
reading manifest file 'src\log_corp2.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp2.egg-info\SOURCES.txt'
installing to build\bdist.win-amd64\wheel
running install
running install_lib
creating build\bdist.win-amd64\wheel
creating build\bdist.win-amd64\wheel\log_corp
copying build\lib\log_corp\module2.py -> build\bdist.win-amd64\wheel\.\log_corp
creating build\bdist.win-amd64\wheel\log_corp\sub_pkg2
copying build\lib\log_corp\sub_pkg2\sub_module2.py -> build\bdist.win-amd64\wheel\.\log_corp\sub_pkg2
running install_egg_info
Copying src\log_corp2.egg-info to build\bdist.win-amd64\wheel\.\log_corp2-1.0.2-py3.11.egg-info
running install_scripts
creating build\bdist.win-amd64\wheel\log_corp2-1.0.2.dist-info\WHEEL
creating 'E:\Documents\log_corp_comp2\dist\.tmp-bn0t41tx\log_corp2-1.0.2-py3-none-any.whl' and adding 'build\bdist.win-amd64\wheel' to it
adding 'log_corp/module2.py'
adding 'log_corp/sub_pkg2/sub_module2.py'
adding 'log_corp2-1.0.2.dist-info/METADATA'
adding 'log_corp2-1.0.2.dist-info/WHEEL'
adding 'log_corp2-1.0.2.dist-info/top_level.txt'
adding 'log_corp2-1.0.2.dist-info/RECORD'
removing build\bdist.win-amd64\wheel
Successfully built log_corp2-1.0.2.tar.gz and log_corp2-1.0.2-py3-none-any.whl
PS E:\Documents\log_corp_comp2>

Using log_corp

Creating virtual environment.

Press enter or click to view image in full size

Installing one component of log-corp

Press enter or click to view image in full size

Installing another component of log-corp

Press enter or click to view image in full size

So we have installed 2 different packages that share same namespace (main package). So, now these two packages log_corp1 and log_corp2 can be develop, distribute, install, upgrade, version independently. (Still sharing the same namespace, so called Namespace packages.)

Here the merging of the sources is automatically done during the installation and there is no conflicts during the merge. This is one of the major benefits of using Namespace packages.

Press enter or click to view image in full size

The one notable disadvantage is that import * to the log_corp won’t work as expected.

Press enter or click to view image in full size

Since there is no __init__.py file to initialize the package.

Trying to achieve the same using the regular package

Adding __init__.py for both the components and have some initialization in that file.

Component 1

LOG_CORP_COMP1/
├── src/
│ └── log_corp/
│ ├── sub_pkg1/
│ │ ├── __init__.py
│ │ └── sub_module1.py
│ ├── __init__.py
│ └── module1.py
├── pyproject.toml
└── README.md

log_corp/__init__.py

from . import module1
from . import sub_pkg1

log_corp/sub_pkg1/__init__.py

from . import sub_module1

Rest of the files has the same content.

Component 2

LOG_CORP_COMP2/
├── src/
│ └── log_corp/
│ ├── sub_pkg2/
│ │ ├── __init__.py
│ │ └── sub_module2.py
│ ├── __init__.py
│ └── module2.py
├── pyproject.toml
└── README.md

log_corp/__init__.py

from . import module2
from . import sub_pkg2

log_corp/sub_pkg2/__init__.py

from . import sub_module2

Rest of the files has the same content.

Building both the packages

Component 1

PS C:\Users\L\Desktop\log_corp_comp1> python -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools
* Getting build dependencies for sdist...
running egg_info
creating src\log_corp1.egg-info
<...truncated output>
removing build\bdist.win-amd64\wheel
Successfully built log_corp1-1.0.1.tar.gz and log_corp1-1.0.1-py3-none-any.whl
PS C:\Users\L\Desktop\log_corp_comp1>
PS C:\Users\L\Desktop\log_corp_comp1> ls


Directory: C:\Users\L\Desktop\log_corp_comp1


Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 12/28/2024 1:31 PM dist
d----- 12/28/2024 1:31 PM src
-a---- 12/28/2024 11:41 AM 387 pyproject.toml
-a---- 12/28/2024 11:31 AM 34 README.md


PS C:\Users\L\Desktop\log_corp_comp1>

Component 2

PS E:\Documents\log_corp_comp2> python -m build
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools
* Getting build dependencies for sdist...
running egg_info
creating src\log_corp2.egg-info
writing src\log_corp2.egg-info\PKG-INFO
writing dependency_links to src\log_corp2.egg-info\dependency_links.txt
writing top-level names to src\log_corp2.egg-info\top_level.txt
writing manifest file 'src\log_corp2.egg-info\SOURCES.txt'
reading manifest file 'src\log_corp2.egg-info\SOURCES.txt'
writing manifest file 'src\log_corp2.egg-info\SOURCES.txt'
* Building sdist...
running sdist
running egg_info
<...truncated output>
Writing log_corp2-1.0.2\setup.cfg
Creating tar archive
removing 'log_corp2-1.0.2' (and everything under it)
* Building wheel from sdist
* Creating isolated environment: venv+pip...
* Installing packages in isolated environment:
- setuptools
* Getting build dependencies for wheel...
running egg_info
<...truncated output>
Successfully built log_corp2-1.0.2.tar.gz and log_corp2-1.0.2-py3-none-any.whl
PS E:\Documents\log_corp_comp2>

Usage

Uninstalled the previoulsy installed packages.

Press enter or click to view image in full size

Installing package log_corp1

Press enter or click to view image in full size

using it in a script.

Press enter or click to view image in full size
module1 and subpkg1 is accessible

Installing package log_corp2

Press enter or click to view image in full size

This installation acually overrides the log_corp/__init__.py file.

Now only the modules and subpackages from log_corp2 is available.

Press enter or click to view image in full size

But still we can access the packages/modules from the log_corp1 with the log_corp as the main package.

Press enter or click to view image in full size

The only thing to notice is that the log_corp/__init__.py is override by the log_corp2. So, if we have any critical initialization from log_corp1 will be affected which might lead to the package broken issue.

Conclusion

Namespace packages are a modern way to create flexible and extensible Python packages. They allow multiple parts of a package to reside in different directories or be distributed across multiple projects. This makes them ideal for large-scale projects, plugin systems, or situations where components are developed independently.

Behavior with import *

Namespace packages do not directly support wildcard imports (from package import *) because they lack an __init__.py file, where regular packages usually define the __all__ list to control what is imported.

Pros of Namespace Packages

  1. Modularity: Different parts of the package can be split across multiple projects or directories.
  2. Scalability: Ideal for large systems with plugins or independently developed components.
  3. Ease of Maintenance: Enables updates to specific parts of a package without affecting the whole structure.
  4. No __init__.py Requirement: Simplifies the creation of packages and avoids unnecessary boilerplate files.
  5. Improved Collaboration: Teams can work on different parts of the package in parallel, with each team owning its directory.

Cons of Namespace Packages

  1. No __init__.py Features: Without an __init__.py file, you lose the ability to initialize the package, define default imports, or set up package-level logic.
  2. Complexity in Wildcard Imports: Does not support import *, leading to potential inconvenience in some use cases.
  3. Runtime Composition: Resolving the full package structure happens at runtime, which may cause subtle issues if directories are misconfigured.
  4. Debugging Challenges: With parts of the package spread across multiple locations, debugging and tracking down issues can be harder.

Use namespace packages for distributed, modular systems or frameworks. For simpler projects or when compatibility with older Python versions is required, stick to regular packages.

Hope you have learned something new today!

Happie Learning!!

Cheers!!!

--

--

Logesh
Logesh

Written by Logesh

software_engineer@wireless_and_iot_domain

Towards Dev
Towards Dev

Published in Towards Dev

A publication for sharing projects, ideas, codes, and new theories.