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:
You can import it from another file:
The if __name__ == "__main__": block lets the file act as a script during development without running that code when the module is imported elsewhere.
Turning Related Modules into a Package
When the code grows, create a package directory. A minimal structure might look like this:
Example contents:
stats.py
geometry.py
__init__.py
With that file in place, callers can write:
__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:
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:
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:
Then install it in editable mode while developing:
Editable installation lets you update module code without reinstalling after every change.
After installation, verify imports from outside the package directory:
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:
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
.pyfile, while a package is a directory of related modules. - Use
__init__.pyto mark package structure and expose a clean public API. - Prefer relative imports inside a package to keep internal references stable.
- Add
pyproject.tomlif the package should be installable. - Keep the API deliberate and avoid mixing reusable modules with path-dependent script behavior.

