Last updated: 2026-05-22

Python modules and packages

As Python projects grow, keeping all code in a single file becomes unmanageable. Modules and packages are Python's solution — they let you split code across multiple files, organise related functionality together, and reuse code across projects. They are also how Python's enormous ecosystem of third-party libraries is delivered and consumed.


What Is a Module?

A module is simply a Python file — any .py file is a module. The filename (without the .py extension) becomes the module name. A module can contain functions, classes, variables, and executable code.

my_project/
├── main.py
├── utils.py        ← a module
└── database.py     ← a module

Importing a Module

To use code from another module, import it with the import statement:

# utils.py
def greet(name):
    return f"Hello, {name}!"

PI = 3.14159
# main.py
import utils

print(utils.greet("Wariz"))    # Hello, Wariz!
print(utils.PI)                # 3.14159

Importing a module gives you access to its contents via the module name as a namespace — utils.greet, utils.PI. This avoids name collisions if two modules define a function with the same name.


Import Variations

Import specific names — from ... import

from utils import greet, PI

print(greet("Wariz"))    # No "utils." prefix needed
print(PI)

This imports specific names directly into the current namespace. Use this when you need only a few things from a module and the names are unambiguous.

Import with an alias — as

import utils as u
print(u.greet("Wariz"))

from utils import greet as say_hello
print(say_hello("Wariz"))

Aliases are commonly used for modules with long names or widely adopted shorthand conventions:

import numpy as np          # Convention in data science
import pandas as pd         # Convention in data science
import matplotlib.pyplot as plt

Import everything — from module import *

from utils import *    # Imports all public names
print(greet("Wariz"))

This approach is generally discouraged — it pollutes the current namespace with unknown names, making it hard to know where a name came from and creating potential conflicts.


The if __name__ == "__main__" Guard

When Python runs a file directly, it sets the special variable __name__ to "__main__". When a file is imported as a module, __name__ is set to the module's name instead.

This means the guard lets you write code that only runs when the script is executed directly — not when it is imported:

# utils.py
def greet(name):
    return f"Hello, {name}!"

if __name__ == "__main__":
    # This block only runs when utils.py is run directly
    # It does NOT run when utils is imported by main.py
    print(greet("Test"))

This pattern prevents side effects (like printing output or running tests) from executing when a module is imported by another file.


Python's Standard Library

Python ships with a large standard library — a collection of built-in modules that are always available without installation. A few of the most commonly used:

ModuleWhat It Provides
osInteracting with the operating system — file paths, environment variables, directory operations
sysAccess to interpreter settings — command-line arguments, Python version, exit codes
mathMathematical functions — sqrt, ceil, floor, log, trigonometry
randomRandom number generation and random selection
datetimeWorking with dates, times, and time differences
jsonEncoding and decoding JSON data
reRegular expressions for pattern matching in strings
pathlibModern, object-oriented file path handling
collectionsSpecialised container types — Counter, defaultdict, deque, namedtuple
itertoolsTools for working with iterators — combinations, permutations, chaining
functoolsHigher-order function tools — partial, reduce, lru_cache
typingType hints and annotations

Examples

import math
print(math.sqrt(16))      # 4.0
print(math.pi)            # 3.141592653589793

import random
print(random.randint(1, 10))         # Random integer between 1 and 10
print(random.choice(["a", "b", "c"])) # Random item from list

import datetime
today = datetime.date.today()
print(today)    # 2024-03-17

from pathlib import Path
p = Path("my_folder") / "data" / "file.txt"
print(p)    # my_folder/data/file.txt

What Is a Package?

A package is a directory that contains multiple modules and a special file called __init__.py. The __init__.py file (which can be empty) tells Python that the directory should be treated as a package.

my_project/
├── main.py
└── mypackage/
    ├── __init__.py      ← makes this a package
    ├── auth.py
    ├── database.py
    └── utils.py

Importing from a package:

import mypackage.auth
from mypackage.utils import greet
from mypackage import database

__init__.py

The __init__.py file runs when the package is imported. It can be empty or it can define what gets exported when someone does from mypackage import *:

# mypackage/__init__.py
from .utils import greet
from .auth import login, logout

# Now users can do:
# from mypackage import greet, login

The . before a module name in __init__.py is a relative import — it refers to modules within the same package.


Relative vs. Absolute Imports

Absolute imports

Reference the full path from the project root:

from mypackage.utils import greet
from mypackage.auth import login

Relative imports

Reference relative to the current module's location using .:

# Inside mypackage/auth.py
from .utils import greet        # Same package
from ..other import something   # Parent package
  • . = current package
  • .. = parent package

Absolute imports are generally preferred — they are more explicit and work regardless of where the module is executed from. Relative imports are useful within a package to avoid repeating the package name.


Third-Party Packages with pip

Beyond the standard library, Python's ecosystem includes over 500,000 packages on PyPI (the Python Package Index). These are installed with pip:

pip install requests          # HTTP library
pip install flask             # Web framework
pip install pandas            # Data analysis
pip install numpy             # Numerical computing

Once installed, they are imported exactly like standard library modules:

import requests
response = requests.get("https://api.github.com")
print(response.status_code)    # 200

Managing dependencies with requirements.txt

When sharing a project, list its dependencies in a requirements.txt file so others can install everything with one command:

# Generate from the current environment
pip freeze > requirements.txt

# Install all dependencies
pip install -r requirements.txt

A typical requirements.txt:

flask==3.0.2
requests==2.31.0
python-dotenv==1.0.1

Exploring a Module

To see everything a module provides:

import math

dir(math)       # Lists all attributes and functions
help(math)      # Full documentation
help(math.sqrt) # Documentation for a specific function

dir() is particularly useful when you are exploring a new library and want to know what tools are available.


Summary

ConceptWhat It Is
ModuleAny .py file — importable by name
PackageA directory containing __init__.py and modules
import moduleImports the module — access via module.name
from module import nameImports specific names into current namespace
import module as aliasImports with a shorthand name
__name__"__main__" when run directly; module name when imported
Standard libraryBuilt-in modules — always available, no installation needed
PyPIPython Package Index — source of third-party packages
pip installInstalls third-party packages
requirements.txtLists project dependencies for reproducible installs
Absolute importFull path from project root
Relative import.module syntax — relative to current package location