Previous | Tutorial index | Next
Write and use your own modules and packages.
In Tutorial 6, you learned how to create individual modules (.py files) to organize your code. But what happens when your project grows to dozens or even hundreds of modules? Throwing them all into one directory becomes chaotic. You need a hierarchical structure to group related modules together.
Packages are the solution. They allow you to organize modules into directories, creating a logical, hierarchical namespace. Think of a package as a folder containing related modules – like having separate folders for "utilities," "database," and "web" in your project. Packages are the standard way to structure large Python projects and libraries.
In this tutorial, you will learn how to create your own packages, leverage __init__.py for powerful initialization and API design, understand the nuances of relative versus absolute imports, and finally, learn how to distribute your packages or install third-party packages using pip.
Before diving in, let's reinforce the distinction:
| Feature | Module | Package |
|---|---|---|
| File System | A single .py file. |
A directory (folder). |
| Required File | Only the .py file itself. |
Must contain an __init__.py file (for regular packages). |
| Purpose | Groups related functions, classes, and variables for a specific task. | Groups related modules and sub-packages for a broader domain. |
| Namespace Example | import math (where math.py is the file). |
import xml.etree (where xml is a package, etree is a sub-package). |
| Analogy | A single book (e.g., "Math Handbook"). | A bookshelf or library section containing many books. |
Create a new directory for your package. The name of this directory will be the name of your package. Use snake_case (e.g., my_package, data_utils).
__init__.pyInside the directory, create an empty file named __init__.py. This file is required for Python to recognize the directory as a package. It can be empty, but it serves as the package's "entry point."
Add your .py module files inside the directory.
Let's create a package called shapes:
shapes/
├── __init__.py # Marks this as a package
├── circle.py # Module for circle calculations
└── rectangle.py # Module for rectangle calculations
shapes/circle.py:
# shapes/circle.py
import math
def area(radius):
"""Calculate the area of a circle."""
return math.pi * radius ** 2
def circumference(radius):
"""Calculate the circumference of a circle."""
return 2 * math.pi * radius
shapes/rectangle.py:
# shapes/rectangle.py
def area(length, width):
"""Calculate the area of a rectangle."""
return length * width
def perimeter(length, width):
"""Calculate the perimeter of a rectangle."""
return 2 * (length + width)
Now, from a script in the parent directory of shapes/, you can import and use the package:
# main.py (in the same directory as the 'shapes' folder)
import shapes.circle
import shapes.rectangle
print(shapes.circle.area(5)) # 78.53981633974483
print(shapes.rectangle.area(4, 6)) # 24
Or, using from:
from shapes import circle, rectangle
print(circle.area(5))
print(rectangle.area(4, 6))
__init__.py – Beyond Just Marking a PackageThe __init__.py file is executed automatically when the package or any module inside it is imported. It is not just a marker; it's a powerful tool for package design.
You can place initialization code in __init__.py that runs once when the package is first imported. This is useful for:
Example:
# shapes/__init__.py
print("Initializing the shapes package...")
# Package-level configuration
DEBUG = True
VERSION = "1.0.0"
# Logging setup (if needed)
import logging
logging.basicConfig(level=logging.INFO)
One of the most powerful uses of __init__.py is to pre-import key functions from submodules into the package namespace. This allows users to call functions directly from the package level, without needing to know the internal module structure.
Before (without pre-imports):
import shapes.circle
import shapes.rectangle
area_circle = shapes.circle.area(5)
area_rect = shapes.rectangle.area(4, 6)
After (with pre-imports in __init__.py):
# shapes/__init__.py
from .circle import area as circle_area
from .rectangle import area as rect_area
Now the user can write:
import shapes
print(shapes.circle_area(5)) # Clean and intuitive!
print(shapes.rect_area(4, 6))
Note: The . before circle means "from the same package/current directory." This is a relative import (discussed in detail later).
__all__When a user writes from shapes import *, Python checks the __all__ variable in __init__.py to determine which modules to import. If __all__ is not defined, from shapes import * imports nothing (only the package itself).
Example:
# shapes/__init__.py
__all__ = ['circle', 'rectangle'] # Only these modules will be imported with *
Now, from shapes import * imports the circle and rectangle modules. This gives you fine-grained control over your package's public interface.
A well-designed __init__.py often combines all three techniques:
# shapes/__init__.py
# 1. Documentation
"""
shapes – A package for calculating properties of geometric shapes.
This package provides modules for circles, rectangles, and more.
"""
# 2. Initialization
print("Initializing shapes package...")
VERSION = "1.0.0"
# 3. Simplify API (pre-import key functions)
from .circle import area as circle_area, circumference as circle_circ
from .rectangle import area as rect_area, perimeter as rect_perimeter
# 4. Control wildcard imports
__all__ = ['circle_area', 'circle_circ', 'rect_area', 'rect_perimeter']
Now the user can use the package elegantly:
import shapes
print(shapes.circle_area(5)) # 78.53981633974483
print(shapes.rect_area(4, 6)) # 24
print(shapes.__version__) # "1.0.0"
There are several ways to import from a package:
import shapes.circle
import shapes.rectangle
shapes.circle.area(5)
shapes.rectangle.area(4, 6)
from shapes import circle, rectangle
circle.area(5)
rectangle.area(4, 6)
__init__.py)from shapes import circle_area, rect_area
circle_area(5)
rect_area(4, 6)
*)from shapes import * # Only what's in __all__ is imported
circle.area(5) # Works if 'circle' is in __all__
If you have nested packages (e.g., shapes/3d/sphere.py):
from shapes.3d import sphere
sphere.volume(5)
When importing modules within the same package, you have two options: absolute imports and relative imports.
An absolute import specifies the full path from the top-level package.
Example:
# Inside shapes/circle.py
from shapes.rectangle import perimeter # Absolute import
Pros: Explicit, clear, works from anywhere. Cons: Verbose, breaks if the package is renamed.
A relative import uses dots (.) to refer to the current and parent packages.
| Syntax | Meaning |
|---|---|
. |
The current package. |
.. |
The parent package. |
... |
The grandparent package, etc. |
Example:
# Inside shapes/circle.py
from .rectangle import perimeter # Relative import (from the same package)
Pros: Concise, portable (no need to repeat package names). Cons: Cannot be used in scripts run directly (only inside packages).
Important: Relative imports only work inside packages where the __name__ is not "__main__". If you try to run circle.py directly (python circle.py), a relative import will raise an ImportError because the interpreter doesn't know the package context.
Best Practice: Many Python projects use absolute imports exclusively for clarity and to avoid confusion.
pipWhile you are learning to create your own packages, you will also frequently need to use packages created by others. The standard tool for installing third-party Python packages is pip.
pip Commands| Command | Description |
|---|---|
pip install package_name |
Installs the latest version of a package. |
pip install package_name==1.2.3 |
Installs a specific version. |
pip install package_name>=1.2.0 |
Installs a minimum version. |
pip uninstall package_name |
Removes a package. |
pip list |
Lists all installed packages. |
pip show package_name |
Shows detailed information about a package. |
pip freeze > requirements.txt |
Saves a list of all installed packages to a file. |
pip install -r requirements.txt |
Installs packages from a requirements file. |
requestsThe requests package is a popular library for making HTTP requests.
pip install requests
Then, in your Python script:
import requests
response = requests.get("https://api.github.com")
print(response.status_code)
It's strongly recommended to use virtual environments to isolate package dependencies for different projects. This prevents conflicts between projects (e.g., Project A needs django==2.0, Project B needs django==3.0).
# Create a virtual environment
python -m venv myenv
# Activate it
# On Windows:
myenv\Scripts\activate
# On Mac/Linux:
source myenv/bin/activate
# Install packages
pip install requests numpy pandas
Once you have created a package, you might want to share it with the world. Python has a central repository called the Python Package Index (PyPI) where you can upload your packages so others can install them with pip.
To prepare a package for distribution, you typically add additional files:
shapes/
├── shapes/ # Your package code
│ ├── __init__.py
│ ├── circle.py
│ └── rectangle.py
├── setup.py # Package metadata (name, version, author, etc.)
├── README.html # Description of your package
└── LICENSE.txt # License information
setup.py Example# setup.py
from setuptools import setup, find_packages
setup(
name="shapes-package",
version="1.0.0",
author="Your Name",
description="A simple package for shape calculations.",
packages=find_packages(),
install_requires=[], # Dependencies, e.g., "numpy>=1.18.0"
)
While developing, you can install your package in "editable" mode, so changes are reflected immediately:
pip install -e .
This makes your package importable from anywhere on your system, just like any installed package.
Note: Publishing packages to PyPI is an advanced topic. For now, focus on creating well-structured packages for your own projects.
1. What file is required to mark a directory as a Python package?
main.py__init__.pysetup.pypackage.json2. How do you make a function circle_area from circle.py available as shapes.circle_area() when someone imports the shapes package?
circle_area.from .circle import area as circle_area to __init__.py.__main__.py.__all__ = ['circle_area'] in circle.py.3. Which of the following is a relative import?
import shapes.circlefrom shapes import circlefrom .circle import areafrom shapes.circle import area4. What does pip freeze do?
5. What is the correct way to install a package named requests using pip?
pip install requestspip download requestsinstall requestspip get requests6. True or False: The __init__.py file is executed every time you import any module from the package.
7. If you define __all__ = ['circle'] in __init__.py, what happens when a user runs from shapes import *?
circle module is imported.NameError is raised.* is ignored.8. Can relative imports be used in a script that is run directly (e.g., python circle.py)?
ImportError.shapes PackageGoal: Follow the instructions from the core content and create a fully functional shapes package.
Instructions:
shapes.__init__.py, circle.py, and rectangle.py with the code provided above.__init__.py, import circle and rectangle so that users can use them directly.__all__ to control wildcard imports.main.py script in the parent directory that imports and uses the package in all three ways:
import shapes.circlefrom shapes import circle, rectanglefrom shapes import * (if you set __all__ correctly)Refer to the code provided in the tutorial sections; the final __init__.py and main.py are shown in the appendix of the tutorial.
Goal: Extend the shapes package with a new module for triangles.
Instructions:
shapes/triangle.py with functions:
area(base, height) – returns 0.5 * base * height.perimeter(side1, side2, side3) – returns the sum of the three sides.__init__.py, import and expose area as triangle_area and perimeter as triangle_perimeter.__all__ to include these new names.triangle.py:
def area(base, height):
return 0.5 * base * height
def perimeter(s1, s2, s3):
return s1 + s2 + s3
Update in init.py:
from .triangle import area as triangle_area, perimeter as triangle_perimeter
__all__ = ['circle_area', 'circle_circ', 'rect_area', 'rect_perimeter', 'triangle_area', 'triangle_perimeter']
Goal: Add a sub-package for 3D shapes.
Instructions:
shapes/3d/.shapes/3d/, add __init__.py and sphere.py with a function volume(radius) (return 4/3 * math.pi * radius**3).shapes/3d/__init__.py, import volume from sphere and expose it as sphere_volume.shapes.3d and calculate the volume of a sphere with radius 3.shapes/3d/sphere.py:
import math
def volume(radius):
return 4/3 * math.pi * radius**3
shapes/3d/init.py:
from .sphere import volume as sphere_volume
Usage:
from shapes import sphere_volume
print(sphere_volume(3))
pip to Install a PackageGoal: Practice using pip to install and use a third-party package.
Instructions:
requests package: pip install requests.requests.get() to fetch the HTML of https://www.python.org and prints the status code.import requests
response = requests.get("https://www.python.org")
print(f"Status code: {response.status_code}")
| Problem | Likely Cause | Solution |
|---|---|---|
ImportError: No module named 'my_package' |
The package is not in the current directory or sys.path. |
Ensure you are in the parent directory of the package. Add the path to sys.path if needed. |
ImportError: attempted relative import with no known parent package |
You tried to use a relative import in a script that is run directly (not inside a package). | Use absolute imports, or restructure so the script is part of a package and run with python -m package.module. |
ModuleNotFoundError: No module named 'shapes.circle' |
The module file does not exist, or __init__.py is missing. |
Check the file structure and ensure circle.py is in the shapes/ directory with __init__.py. |
The __init__.py code is not running. |
Python may be using a cached version of the package. | Delete the __pycache__ folder and restart the interpreter. |
pip install fails with permission errors. |
You don't have write permissions to the global site-packages. | Use a virtual environment, or run pip install --user package_name to install only for your user. |
| The package installs but cannot be imported. | The package name might not match the import name (e.g., you installed shapes-package but imported shapes). |
Check the package's documentation for the correct import name. Use pip show package_name to see metadata. |
| Relative imports work in one place but fail in another. | You may have a different entry point for your script (e.g., running from outside the package). | Standardize on absolute imports for clarity and reliability. |
Answer the following questions in complete sentences. Each response should be 3–5 sentences unless otherwise specified.
1. What is the role of __init__.py in a Python package? Give three distinct uses.
2. Explain the difference between absolute and relative imports within a package. When would you prefer one over the other?
3. What is a virtual environment, and why is it recommended when using pip to install packages?
4. How does the __all__ variable affect the behavior of from package import *?
Answer the following questions in 300–500 words each.
5. Discuss the design decisions involved in creating a Python package, including directory structure, the use of __init__.py, and the choice between absolute and relative imports. How do these decisions affect the usability and maintainability of the package?
Suggested outline:
__init__.py: Initialization, API simplification, __all__.6. Compare and contrast the process of creating a single module versus creating a package. When would you choose one over the other? Provide examples of projects that would benefit from each approach.
Suggested outline:
These questions require additional research beyond the tutorial content.
7. Research the concept of "namespace packages" in Python. How do they differ from regular packages, and in what scenarios would you use them?
8. Investigate the structure of a popular Python package (e.g., requests, numpy, flask). Look at its __init__.py and how it exports its API. Write a short summary of what you observe and how it affects the user's experience.
__init__.py file.__init__.py, and placing .py module files inside.__init__.py file serves multiple critical roles:
__all__ to control wildcard imports.import package.module, from package import module, or from package import * (controlled by __all__).. and ..) are used within a package to import other modules in the same or parent packages. They do not work in scripts run directly.pip tool is used to install, manage, and uninstall third-party packages from PyPI.__init__.pyfrom .circle import area as circle_area to __init__.py.from .circle import area (starts with a dot).pip install requestscircle module is imported (as specified in __all__).ImportError because the package context is unknown.This concludes the expanded Tutorial 7 and the entire series on Python modules and packages. You now have a comprehensive understanding of how to organize, import, create, and distribute reusable Python code. Congratulations on completing this journey!
For reference, here is the complete shapes package as described in the tutorial:
shapes/__init__.py:
"""
shapes – A package for calculating properties of geometric shapes.
This package provides modules for circles, rectangles, and more.
"""
# Package initialization
print("Initializing shapes package...")
VERSION = "1.0.0"
# Simplify API: pre-import key functions
from .circle import area as circle_area
from .circle import circumference as circle_circ
from .rectangle import area as rect_area
from .rectangle import perimeter as rect_perimeter
# Control wildcard imports
__all__ = ['circle_area', 'circle_circ', 'rect_area', 'rect_perimeter']
shapes/circle.py:
"""Module for circle calculations."""
import math
def area(radius):
"""Calculate the area of a circle."""
return math.pi * radius ** 2
def circumference(radius):
"""Calculate the circumference of a circle."""
return 2 * math.pi * radius
shapes/rectangle.py:
"""Module for rectangle calculations."""
def area(length, width):
"""Calculate the area of a rectangle."""
return length * width
def perimeter(length, width):
"""Calculate the perimeter of a rectangle."""
return 2 * (length + width)
main.py (demonstration script):
# main.py (in the parent directory)
import shapes
print(f"Using shapes package version {shapes.VERSION}")
print(f"Circle area (r=5): {shapes.circle_area(5)}")
print(f"Rectangle area (4x6): {shapes.rect_area(4, 6)}")
print(f"Rectangle perimeter (4x6): {shapes.rect_perimeter(4, 6)}")