What is a Package?
A Python package is a way to organize related modules into a directory hierarchy. Think of it as a folder that Python recognizes as containing importable code. Packages help you structure large projects and make your code reusable across different projects.
Module vs Package
A module is a single .py file. A package is a directory containing modules and an __init__.py file. Packages can contain subpackages, creating a hierarchical structure for organizing code.
Simple rule: If you have more than 3-4 related modules, consider grouping them into a package.
Why Use Packages?
Organization
Group related code together logically. Makes large projects manageable.
Namespace Protection
Avoid name collisions between modules with the same name.
Reusability
Share your package across projects or publish it for others to use.
Maintainability
Easier to find, update, and test code when it is well organized.
Package Structure
A package is simply a directory containing an __init__.py file and one or more module files. The __init__.py file can be empty or contain initialization code. Let's see how to create a basic package structure.
Package Structure Diagram
myproject/ # Project root main.py # Entry point (uses the package) mypackage/ # Package directory __init__.py # Makes it a package (can be empty) module1.py # First module module2.py # Second module utils/ # Subpackage __init__.py # Subpackage init helpers.py # Subpackage module # Importing from this structure: from mypackage import module1 from mypackage.module2 import some_function from mypackage.utils.helpers import helper_func
Creating a Simple Package
# File: mypackage/__init__.py
# Can be empty, or define what to export:
from .module1 import greet
from .module2 import calculate
# File: mypackage/module1.py
def greet(name):
return f"Hello, {name}!"
# File: mypackage/module2.py
def calculate(a, b):
return a + b
The __init__.py imports make it easy for users to import directly from the package: from mypackage import greet.
Using the Package
# File: main.py (in project root)
# Method 1: Import from __init__.py exports
from mypackage import greet, calculate
print(greet("Alice")) # Hello, Alice!
print(calculate(5, 3)) # 8
# Method 2: Import specific modules
from mypackage.module1 import greet
from mypackage.module2 import calculate
# Method 3: Import entire package
import mypackage
print(mypackage.greet("Bob")) # Hello, Bob!
How you import depends on what __init__.py exports. Clean __init__.py files make your package easier to use.
Practice: Package Structure
Task: Create a package called math_tools with add.py and multiply.py modules, each with one function.
Show Solution
# math_tools/__init__.py
from .add import add
from .multiply import multiply
# math_tools/add.py
def add(a, b):
return a + b
# math_tools/multiply.py
def multiply(a, b):
return a * b
# main.py
from math_tools import add, multiply
print(add(2, 3)) # 5
print(multiply(4, 5)) # 20
Task: Create a package with an empty __init__.py and show how to import from it.
Show Solution
# myutils/__init__.py
# (empty file)
# myutils/strings.py
def capitalize_all(words):
return [w.upper() for w in words]
# main.py - must import from module directly
from myutils.strings import capitalize_all
print(capitalize_all(["hello", "world"])) # ['HELLO', 'WORLD']
Task: Create a shapes package with a subpackage called two_d containing circle.py and rectangle.py.
Show Solution
# shapes/__init__.py
from .two_d import circle, rectangle
# shapes/two_d/__init__.py
from .circle import area as circle_area
from .rectangle import area as rect_area
# shapes/two_d/circle.py
import math
def area(radius):
return math.pi * radius ** 2
# shapes/two_d/rectangle.py
def area(width, height):
return width * height
# main.py
from shapes.two_d import circle_area, rect_area
print(circle_area(5)) # 78.54...
print(rect_area(4, 6)) # 24
The __init__.py File
The __init__.py file serves multiple purposes. It marks a directory as a Python package, runs initialization code when the package is imported, and controls what gets exported when someone imports from your package.
Empty vs Non-Empty __init__.py
# Empty __init__.py
# Just marks the directory as a package
# Users must import from specific modules:
from mypackage.module1 import func1
# Non-empty __init__.py with exports
# File: mypackage/__init__.py
from .module1 import func1
from .module2 import func2, MyClass
# Now users can import directly:
from mypackage import func1, func2, MyClass
The dot in from .module1 means "from this package". It is called a relative import.
Controlling Exports with __all__
# mypackage/__init__.py
from .module1 import func1, func2, helper
from .module2 import ClassA, ClassB
# Only export these when using "from mypackage import *"
__all__ = ["func1", "ClassA"]
# Now:
# from mypackage import *
# Only imports func1 and ClassA, not func2, helper, or ClassB
__all__ is a list of names that should be exported when someone uses "from package import *". It is optional but useful.
Package Initialization Code
# mypackage/__init__.py
print("Initializing mypackage...") # Runs when imported
# Set package-level variables
VERSION = "1.0.0"
AUTHOR = "Your Name"
# Import and expose functions
from .core import main_function
from .utils import helper
# Run setup code
_setup_logging() # Internal function
Code in __init__.py runs once when the package is first imported. Use it for initialization, configuration, or exposing the public API.
Import Types
Python offers several ways to import from packages. Each style has its use case. Understanding when to use each makes your code cleaner and more readable.
| Import Style | Syntax | Usage |
|---|---|---|
| Import module | import package.module |
package.module.func() |
| Import with alias | import package.module as m |
m.func() |
| From import | from package import module |
module.func() |
| From import item | from package.module import func |
func() |
| Import all | from package import * |
Imports everything (avoid in code) |
Best Practices
# Good: Explicit imports
from mypackage.utils import format_date
from mypackage.models import User, Product
# Good: Short alias for long names
import matplotlib.pyplot as plt
import pandas as pd
# Avoid: Star imports (pollutes namespace)
from mypackage import * # What did we import?
# Avoid: Too many levels
import a.b.c.d.e.f.module # Hard to read
Prefer explicit imports. They make it clear where each name comes from and help IDEs with autocomplete.
Practice: Imports
Task: Given a package "data" with module "loader" containing "load_csv", write 3 different ways to import it.
Show Solution
# Style 1: Import module
import data.loader
data.loader.load_csv("file.csv")
# Style 2: From import
from data import loader
loader.load_csv("file.csv")
# Style 3: Import function directly
from data.loader import load_csv
load_csv("file.csv")
Task: Create a module with 4 functions but only export 2 using __all__.
Show Solution
# mymodule.py
__all__ = ["public_func1", "public_func2"]
def public_func1():
return "I am public"
def public_func2():
return "I am also public"
def _private_helper():
return "I am private by convention"
def internal_func():
return "Not in __all__, not exported with *"
Task: Create an __init__.py that imports from 3 modules and exposes a clean public API with __all__.
Show Solution
# mylib/__init__.py
"""My Library - Clean API example."""
from .config import Config, load_config
from .client import Client, connect
from .utils import format_response
__all__ = [
"Config",
"load_config",
"Client",
"connect",
"format_response"
]
__version__ = "1.0.0"
Relative Imports
Relative imports use dots to refer to modules within the same package. A single dot means "current package" and two dots mean "parent package". They make your package more portable and avoid hardcoding package names.
Single Dot - Current Package
# Package structure:
# mypackage/
# __init__.py
# main.py
# utils.py
# In main.py - import from same package
from . import utils # Import utils module
from .utils import helper # Import helper from utils
# These are equivalent to:
from mypackage import utils
from mypackage.utils import helper
Single dot imports from the current package. This is cleaner and works even if you rename the package.
Double Dot - Parent Package
# Package structure:
# mypackage/
# __init__.py
# core/
# __init__.py
# engine.py
# utils/
# __init__.py
# helpers.py
# In helpers.py - import from parent package
from .. import core # Go up, import core
from ..core import engine # Go up, into core, import engine
from ..core.engine import run # Go up, into core.engine, get run
Two dots go up one level. Three dots go up two levels, and so on. This is useful in subpackages.
When to Use Relative Imports
Use Relative Imports
- Within your own package
- Between subpackages
- In __init__.py files
- When package might be renamed
Use Absolute Imports
- For external packages (numpy, etc.)
- In scripts run directly
- When path is clear/simple
- In your main entry point
Practice: Relative Imports
Task: Convert from mypackage.utils import helper to a relative import (assuming you are in mypackage/main.py).
Show Solution
# Absolute import (original)
from mypackage.utils import helper
# Relative import (converted)
from .utils import helper
# Both work the same when inside mypackage/
Task: In mypackage/sub/module.py, write a relative import to get "config" from mypackage/config.py.
Show Solution
# File: mypackage/sub/module.py
# Import from parent package
from ..config import settings
from .. import config # Or import the whole module
# The .. goes up from "sub" to "mypackage"
Task: From mypackage/api/endpoints.py, import Database from mypackage/db/connection.py.
Show Solution
# File: mypackage/api/endpoints.py
# Structure:
# mypackage/
# api/endpoints.py <- we are here
# db/connection.py <- we want Database from here
# Go up to mypackage, then down to db.connection
from ..db.connection import Database
# Or import the module
from ..db import connection
db = connection.Database()
Key Takeaways
Packages Organize Code
A package is a directory with __init__.py. Use packages to group related modules.
__init__.py Controls API
Use __init__.py to define what gets exported when someone imports your package.
__all__ Limits Exports
Define __all__ to control what "from package import *" exports.
Explicit Imports Best
Prefer explicit imports over star imports. They are clearer and easier to maintain.
Dots Mean Relative
Single dot is current package. Double dot is parent. Use for intra-package imports.
Nest Thoughtfully
Subpackages add organization but also complexity. Do not over-nest.
Knowledge Check
Quick Quiz
Test what you've learned about Python packages