Python
Programming
Software Development
Python Modules
Python Packages

How to write a Python module/package?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A Python module is a single .py file. A Python package is a directory that groups related modules into a reusable unit. In day-to-day development, you usually start with one module and turn it into a package when the code needs clearer structure or a public API.

The best way to think about this is simple: modules organize code inside a file, packages organize code across files.

Writing a Simple Module

A module is just a file containing functions, classes, and constants. For example, a file named math_utils.py might look like this:

python
1"""Utility functions for small arithmetic tasks."""
2
3def average(values):
4    if not values:
5        raise ValueError("values must not be empty")
6    return sum(values) / len(values)
7
8
9def clamp(value, low, high):
10    return max(low, min(value, high))
11
12
13if __name__ == "__main__":
14    sample = [10, 15, 20]
15    print(average(sample))

You can import it from another file:

python
1from math_utils import average, clamp
2
3print(average([1, 2, 3]))
4print(clamp(12, 0, 10))

The if __name__ == "__main__": block lets the file act as a script during development without running that code when the module is imported elsewhere.

When the code grows, create a package directory. A minimal structure might look like this:

text
1coolmath/
2    __init__.py
3    stats.py
4    geometry.py

Example contents:

stats.py

python
1def mean(values):
2    if not values:
3        raise ValueError("values must not be empty")
4    return sum(values) / len(values)

geometry.py

python
1import math
2
3def circle_area(radius):
4    return math.pi * radius ** 2

__init__.py

python
from .stats import mean
from .geometry import circle_area

With that file in place, callers can write:

python
1from coolmath import mean, circle_area
2
3print(mean([4, 6, 8]))
4print(circle_area(3))

__init__.py is often used to expose a clean package-level API. Even when it is nearly empty, it is a useful place to define what the package wants consumers to import.

Import Style and Project Layout

Inside a package, use relative imports to reference sibling modules:

python
from .stats import mean

That keeps internal references tied to the package instead of depending on the current working directory.

For larger projects, a src layout is often cleaner:

text
1project-root/
2    pyproject.toml
3    src/
4        coolmath/
5            __init__.py
6            stats.py
7            geometry.py
8    tests/

This layout helps catch accidental imports from the project root during development.

Packaging for Reuse

If you want the package to be installable, add a pyproject.toml file. A minimal modern example is:

toml
1[build-system]
2requires = ["setuptools>=68", "wheel"]
3build-backend = "setuptools.build_meta"
4
5[project]
6name = "coolmath"
7version = "0.1.0"
8description = "Small math helpers"
9requires-python = ">=3.10"

Then install it in editable mode while developing:

bash
python -m pip install -e .

Editable installation lets you update module code without reinstalling after every change.

After installation, verify imports from outside the package directory:

bash
python -c "from coolmath import mean; print(mean([2, 4, 6]))"

That small check catches many packaging mistakes early, especially when local development previously worked only because the current directory was on sys.path.

Designing a Good Package API

A package should make the common path easy. If users always need coolmath.stats.mean, you may want to re-export that function from __init__.py so they can write from coolmath import mean.

At the same time, avoid exposing every internal helper. Public APIs should be deliberate. Private implementation details can stay in internal modules or use a leading underscore naming convention when appropriate.

Docstrings are worth adding early because they become the first layer of documentation:

python
1def mean(values):
2    """Return the arithmetic mean of a non-empty sequence."""
3    if not values:
4        raise ValueError("values must not be empty")
5    return sum(values) / len(values)

Common Pitfalls

One common mistake is confusing scripts with packages. A file can be executable and importable, but a reusable package should not depend on being run from one specific directory.

Another issue is missing or messy imports. Absolute imports from the wrong root can appear to work locally and then fail after installation. Use package-relative imports inside the package.

Developers also often overload __init__.py with too much logic. It should usually define exports, version metadata, or light initialization, not perform expensive runtime work.

Finally, avoid structuring packages around too many tiny files too early. Start simple. Split modules when the code genuinely becomes easier to read, test, and import.

Summary

  • A module is one .py file, while a package is a directory of related modules.
  • Use __init__.py to mark package structure and expose a clean public API.
  • Prefer relative imports inside a package to keep internal references stable.
  • Add pyproject.toml if the package should be installable.
  • Keep the API deliberate and avoid mixing reusable modules with path-dependent script behavior.

Course illustration
Course illustration

All Rights Reserved.