Python Modules and Packages Tutorial: Imports, Structure and Runnable Examples

Learn what Python imports bind, how a regular package fits together, and why package entry points should run with `python -m`. Build and trace every example yourself.

KnowledgeGate Team

Exam prep & CS education

Updated 26 Sep 20266 min read

Your first two-file Python project may work only when you launch it from one particular directory, or you may know the spelling of import without being able to explain what a module, package or namespace contains. A module is a reusable Python file with its own namespace; a regular package groups modules behind a directory-level interface. import binds names. In the four-file route project, routekit/__init__.py exposes one public function and python -m routekit preserves its package context. The Coding & Skill Development Courses category maps the wider programming path.

Python modules turn one file into reusable code

Create these two files in the same project/ directory.

calculator.py:

python
TAX_RATE = 0.18

def total_with_tax(price):
    return price + price * TAX_RATE

app.py:

python
import calculator

print(calculator.TAX_RATE)
print(f"{calculator.total_with_tax(250):.2f}")

Run python app.py from project/. The output is:

Code
0.18
295.00

The tax is 250 * 0.18 = 45.00, so the total is 250 + 45.00 = 295.00. The file calculator.py is the module; calculator is the module object bound in app.py. Its namespace holds TAX_RATE and total_with_tax, so qualified access shows each name's source.

Trace of import calculator in app.py reaching the calculator namespace, TAX_RATE 0.18 and total_with_tax, then printing 295.00.

Python import forms bind different names

These three forms calculate the same result but bind different names in the importing file:

python
import calculator
print(f"{calculator.total_with_tax(250):.2f}")

from calculator import total_with_tax
print(f"{total_with_tax(250):.2f}")

import calculator as calc
print(f"{calc.total_with_tax(250):.2f}")

All three lines print 295.00. The bound names are calculator, total_with_tax and calc, respectively. An ordinary import first reuses a matching module already cached in sys.modules. Otherwise, Python uses its import finders and the locations represented by sys.path to find and load the module, then caches it for that process. A repeated import normally does not rerun the module's top-level code.

Names can still collide. After from math import sqrt, the assignment sqrt = 9 replaces that local name. Calling sqrt(16) then raises TypeError: 'int' object is not callable. Keep the source explicit instead:

python
import math
print(math.sqrt(16))

This prints 4.0.

__name__ separates reusable imports from direct execution

Add a guarded self-check at the end of calculator.py:

python
TAX_RATE = 0.18

def total_with_tax(price):
    return price + price * TAX_RATE

if __name__ == "__main__":
    print(f"{total_with_tax(100):.2f}")

Running python calculator.py prints 118.00: tax is 100 * 0.18 = 18.00, and 100 + 18.00 = 118.00. Direct execution sets __name__ to "__main__". Importing the file instead gives calculator.__name__ == "calculator", so the guarded self-check does not print during import calculator.

This guard is the right home for demonstrations, command-line entry logic and small ad hoc tests that should run only when the file is executed directly. That separation keeps imports free of surprising output and side effects when several modules reuse the same file. The Python Course: Concepts, MCQs & Coding is a structured route through the wider language if you want to build beyond this example.

Build a Python package with a small public interface

A regular package is a directory containing __init__.py. Build this exact tree under project/:

Code
project/
├── app.py
└── routekit/
    ├── __init__.py
    ├── metrics.py
    └── reports.py

routekit/metrics.py:

python
LONG_SEGMENT_KM = 10.0

def total_distance(distances):
    return sum(distances)

def is_long(distance):
    return distance >= LONG_SEGMENT_KM

This calculation expects at least one distance. In routekit/reports.py, the leading dot imports from the current package:

python
from .metrics import total_distance, is_long

def summary(name, distances):
    total = total_distance(distances)
    long_count = sum(is_long(distance) for distance in distances)
    return f"{name}: total={total:.1f} km, long={long_count}/{len(distances)}"

Expose one intended public name in routekit/__init__.py:

python
from .reports import summary

The caller in app.py can now depend on the package interface instead of its internal filenames:

python
from routekit import summary

print(summary("North Loop", [12.5, 8.0, 9.5]))
print(summary("River Link", [10.0, 10.5, 7.5]))

North Loop totals 12.5 + 8.0 + 9.5 = 30.0 km, and only 12.5 km meets the 10 km threshold. River Link totals 10.0 + 10.5 + 7.5 = 28.0 km; 10.0 km and 10.5 km meet the threshold. The exact output is:

Code
North Loop: total=30.0 km, long=1/3
River Link: total=28.0 km, long=2/3

Importing summary from routekit hides the internal layout from the caller, while the relative imports keep that layout explicit inside the package.

Absolute imports, relative imports and python -m

The caller uses the absolute import from routekit import summary. Inside reports.py, from .metrics import total_distance, is_long is relative, and the dot means the current package. A regular package has __init__.py, which may be empty or may publish a deliberate interface as it does here. Namespace packages can omit that file; a regular package keeps this small project explicit.

Add routekit/__main__.py:

python
from . import summary

print(summary("Ridge Route", [10.0, 6.5, 12.0]))

From project/, run python -m routekit. The sum is 10.0 + 6.5 + 12.0 = 28.5 km, and 10.0 km and 12.0 km meet the threshold. The output is Ridge Route: total=28.5 km, long=2/3.

Do not run python routekit/__main__.py directly. That loses the package context and can raise ImportError: attempted relative import with no known parent package.

Common module and package errors: diagnose before editing imports

Symptom

Likely cause

Correction

ModuleNotFoundError: No module named 'routekit'

The command started from the wrong project level

Move to the directory containing routekit/ and run python app.py again

attempted relative import with no known parent package

A package file was executed directly

From project/, run python -m routekit

random.randint is missing or random is partially initialised

A local random.py shadows the standard-library module

Rename it to dice_tools.py, restart the process and import random normally

A circular import has the same partially initialised shape. If routes.py and reports.py import each other, one side sees the other before initialisation finishes. Move shared constants or neutral helpers into config.py, or pass the required data into functions.

For focused diagnostics, inspect sys.executable, review sys.path, and try importlib.util.find_spec("routekit"). These reveal the interpreter, search locations and discoverability, and are better first checks than arbitrary edits to sys.path.

Python module exercises and realistic code-tracing checks

Useful checks ask you to predict a namespace after import, choose a qualified or direct-name import, trace a main guard, repair a package path, or explain why a circular import exposes an unfinished module. Test those skills with three runnable exercises:

  1. Create temperature.py with c_to_f(c) = c * 9 / 5 + 32. For 25, 25 * 9 / 5 + 32 = 77.0.

  2. Create shop/discount.py with final_price(1000, 15). Compute 1000 - 1000 * 15 / 100 = 850.0, then expose final_price through shop/__init__.py.

  3. Add a shop/__main__.py call for final_price(800, 12.5). Here 800 * 12.5 / 100 = 100.0, so 800 - 100.0 = 700.0. Run it with python -m shop.

After these package exercises, Dynamic Programming Explained: 0/1 Knapsack traces another worked example line by line if you want more code to read.

Python modules and packages: the short version and next step

A module is a Python file. A regular package groups related modules in a directory with __init__.py. Imports load or reuse modules and bind names into namespaces, while python -m preserves package context for runnable entry points. Rebuild the routekit example from an empty directory, predict both output lines, and only then execute it.

For a structured next step across the language, use the Python course linked above. If you can already write functions and want to organise larger problem-solving programs, DSA Using Python is the natural follow-on. Choose the route that matches what you need to practise next.