How to Create and Structure a Python Package
Imagine you have a filing cabinet. Right now, all your papers are thrown in one big drawer — recipes mixed with homework mixed with tax forms. Finding anything is a nightmare.
A Python package is like adding labeled folders to that filing cabinet. Instead of one giant file with all your code, you split it into organized folders where each folder handles one topic.
In this tutorial, you'll learn how Python packages work, what the mysterious __init__.py file does, and how to structure your own projects like a professional developer.
What Is a Python Package?
You already know that a module is a single .py file. A package takes it one step further — it's a folder that contains multiple modules.
The key ingredient that turns a regular folder into a Python package is a special file called __init__.py. When Python sees a folder with this file, it knows: "This folder is a package I can import from."
In a real project, each function above would live in its own file inside the mypackage/ folder. You'd import them like from mypackage.greetings import say_hello.
What Does __init__.py Do?
Think of __init__.py as the front desk of a building. When someone walks in, the front desk tells them what's available and where to go.
The __init__.py file runs automatically whenever you import the package. It can be completely empty, or it can contain code that sets up the package — like importing key functions so users don't have to dig through submodules.
An empty __init__.py is perfectly fine. It just tells Python "this folder is a package." But adding imports to it creates a cleaner experience for anyone using your package.
How Should You Structure a Python Package?
A well-organized package is like a well-organized kitchen. Pots go in one cabinet, spices in another. Anyone walking in can find what they need without opening every drawer.
Here's a typical layout for a Python project with a package inside it:
Notice the src/ folder. This is called the src layout and is recommended by the Python Packaging Authority. It prevents accidental imports from your project root instead of the installed package.
Each sub-folder that has an __init__.py is its own sub-package. You can nest as deep as you need, but try to keep it to 2-3 levels. Deeper nesting usually means your project needs a redesign.
What Are Relative vs Absolute Imports?
When modules inside a package need to import from each other, you have two choices: absolute imports and relative imports.
An absolute import spells out the full path from the top-level package. A relative import uses dots to say "import from nearby." It's like the difference between giving a full street address vs saying "the house next door."
# Inside mypackage/models/user.py
from mypackage.utils import validate_email
from mypackage.core import Database# Inside mypackage/models/user.py
from ..utils import validate_email
from ..core import DatabaseIn relative imports, a single dot . means "current package" and double dots .. mean "parent package." Three dots ... mean "grandparent," and so on.
How Do You Build a Complete Package from Scratch?
Let's walk through building a small but complete package. We'll simulate the file structure in a single script since we're in a browser, but the logic is exactly what you'd put in separate files.
In a real project, each section above would be its own file. The __init__.py would import add, subtract, and format_result so users can access them directly from mathkit.
Practice Exercises
Time to practice! These exercises test your understanding of package structure, __init__.py, and imports.
Create a function called get_package_structure() that returns a dictionary representing a Python package structure. The dictionary should have a key 'webapp' whose value is another dictionary containing these keys (in any order): '__init__.py', 'routes.py', 'models.py', and 'utils.py'. Each value should be the string 'module'.
The code below defines a simulated package with a __all__ list and three functions. The get_public_api() function should return only the names listed in __all__, but there are bugs. Fix them so the output shows exactly the two public functions.
Read the code below carefully. It simulates how Python resolves import paths in a nested package. What will it print?
The code below uses separate variables to track modules in a package. Refactor it into a single class called PackageRegistry with:
__init__ method that sets the package name and an empty dictionary modulesregister(module_name, description) method that adds to modulesget_info() method that returns a formatted string showing the package name and each module on its own line in the format - module_name: descriptionThe output must match exactly.