Module 9.2

Creating Packages

Packages organize code into reusable, shareable modules. As your projects grow beyond a few files, packages help you structure code logically. A package is simply a directory with Python files and a special __init__.py file that tells Python this folder contains importable code.

40 min
Intermediate
Hands-on
What You'll Learn
  • Package structure
  • __init__.py purpose
  • Import types
  • Relative imports
  • Subpackages
Contents
01

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.

Key Concept

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.

02

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
Each folder that should be importable needs its own __init__.py file.

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
03

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.

04

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"
05

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

1 What file makes a directory a Python package?
2 What does a single dot mean in "from . import module"?
3 What is the purpose of __all__ in a module?
4 Which import style is generally preferred?
5 What does "from ..utils import helper" mean?
6 Can __init__.py be empty?
Answer all questions to check your score