A Python package is a directory that contains one or more module files and a special init file named init.py. When you create and use Python packages, you organize related modules under a shared namespace that reflects their conceptual purpose. The mechanics are simple: create a folder, add an init file inside it, place your module files in the folder, and import them with dotted notation.
The real value of packages is not the directory structure itself but the namespace hierarchy it creates. When you previously learned about importing modules in Python, every module lived in a flat namespace. A package lets you nest namespaces so related modules share a parent name. This grouping signals that the modules are related, and it prevents module names from colliding with similarly named modules from other packages or from the standard library.
Package structure on disk
A package is a directory tree. The simplest package consists of a single directory with an init file and a couple of module files. The init file marks the directory as a Python package. Without it, the directory is just an ordinary folder, and attempting to import it will fail with a ModuleNotFoundError. The init file can be completely empty, and in many packages it is. You can create it with a single terminal command.
A package can contain subpackages, which are simply subdirectories with their own init files. A project for data processing might organize its code with a top-level datatools directory containing modules for cleaning and analysis, plus subdirectories for input/output operations and utility functions, each with their own init file. This structure creates multiple importable packages at different levels of the hierarchy, and each module is accessible with a dotted name that mirrors the directory path.
Creating your first package
Start by creating the directory structure. Inside your project folder, make a directory called shapes and add an init file plus three module files: circle.py, rectangle.py, and triangle.py. Each module contains functions related to that specific shape, and the init file starts empty but can later be enhanced with imports that make the package easier to use.
Fill each module with related functions. The circle module contains functions for area and circumference calculations using the mathematical constant pi. The rectangle module provides functions for area and perimeter, plus a check for whether a given width and height form a square:
import math
def area(radius):
return math.pi * radius ** 2
def circumference(radius):
return 2 * math.pi * radiusThe rectangle module follows the same pattern but operates on two parameters, width and height, returning the area and perimeter of the rectangle. Keeping each shape's logic in its own module means that updating the circle formulas never risks accidentally breaking the rectangle code, and testing each module in isolation is straightforward.
Importing from a package
Once the package is in a directory on Python's search path, usually by being in the same directory as your main script, you can import from it using dotted notation. The dot separates levels of the package hierarchy, so importing shapes.circle gives you access to the circle submodule and its functions through the shapes.circle.area syntax.
You can also import specific submodules without loading the entire package into the namespace. Using the from-import style, you can bring in just the circle submodule and access its functions with a short circle. prefix. This style is a good middle ground: you still have a prefix that shows where each function comes from, but you do not need to type the full package path on every call.
from shapes import circle
r = 5
a = circle.area(r)
print(f"Circle area: {a:.2f}")The import executes the package's init file first, which in turn can execute imports that make other submodules available as attributes. After the import, you can access any submodule that the init file exposes, and Python caches the entire package so subsequent imports are instant.
Controlling the public API
When someone uses a wildcard import on your package, Python imports every name from the init file that does not start with an underscore. If your init file contains imports and internal helper variables, the wildcard import dumps all of them into the consumer's namespace indiscriminately. You can control exactly which names are exported by defining a special list variable called all in the init file. This list should contain strings naming every function, class, and constant that you consider part of the package's stable public interface.
With this list defined, the wildcard import imports only the listed names. Any other names defined in the init file are treated as internal and excluded. This does not prevent someone from importing those names explicitly, but it makes the wildcard import behave predictably and signals which names are safe to depend on across version updates. Documentation generators and IDEs also use this list to determine which names to show in autocomplete suggestions.
What to put in the init file
The minimal init file is empty, and for simple packages that is perfectly fine. As packages mature, the init file tends to take on a few specific responsibilities. It can import key names from submodules to flatten the import path for consumers, so they can write a single import statement instead of importing each submodule separately. It can define the public names list to declare the stable API. It can contain package-level constants or configuration that every submodule needs access to.
What you should avoid putting in the init file is heavy computation, network calls, file system operations that write data, or side-effectful code that runs without the consumer's explicit request. Remember that importing any submodule of a package executes the package's init file first. If your init file connects to a database, then importing a single math utility from the package triggers a database connection the consumer did not ask for. Keep init files lightweight and defer expensive setup to explicit function calls or to a separate initialization module. For further guidance on package design, see the article on best practices for Python modules and packages.
Rune AI
Key Insights
- A package is a directory containing an init.py file and one or more module files; subdirectories with their own init files are subpackages.
- The init file can be empty, can re-export names from submodules, or can contain package-level initialization code.
- Use from package.submodule import name to access names deep in a package hierarchy.
- Define the public names list in the init file to control what wildcard imports expose.
- Keep package hierarchies flat: one or two levels of nesting is usually enough.
Frequently Asked Questions
Do I always need an init file in every package directory?
Can I put code inside the init file, or should it always be empty?
How deep should I nest my package directories?
Conclusion
A Python package is a directory of modules with an init file that marks it as importable with dotted notation. Creating a package is as simple as making a folder, adding an init file, and placing module files inside it. The real skill is structuring the package so that its hierarchy mirrors the conceptual organization of the code, making it easy for consumers to find what they need through predictable import paths.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.