Python Modules and Packages

Organize Python code with modules and packages: import styles, creating packages, __name__ guard, __all__, and standard library essentials.

Beginner 11 min read 9 examples

Importing Modules

Python
# import module - access with module.name
import math
print(math.pi)          # 3.141592653589793
print(math.sqrt(16))    # 4.0
print(math.ceil(4.3))   # 5

# import with alias
import datetime as dt
import collections as col
today = dt.date.today()

# from module import name - brings name into current scope
from math import pi, sqrt, ceil
print(pi)       # 3.141592653589793
print(sqrt(9))  # 3.0

# from module import name as alias
from math import sqrt as square_root
print(square_root(25))  # 5.0

# Import multiple from one module
from os.path import join, exists, dirname, basename

# from module import * - avoid in production code
# from math import *   # imports everything - can cause name collisions

# Conditional import - handle missing optional dependency
try:
    import ujson as json    # fast JSON library (optional)
except ImportError:
    import json             # fall back to standard library

data = json.dumps({"key": "value"})

# Lazy import (defer import until needed)
def process_image(path):
    from PIL import Image   # only imported when function is called
    img = Image.open(path)
    return img

# Inspect what's in a module
import os
print(dir(os))          # list of all names in the module
print(os.__file__)      # path to the module file
print(os.__doc__[:80])  # first 80 chars of module docstring

Creating Modules

Any Python file is a module. Name it with lowercase and underscores.

Python
# mymath.py - a simple module

"""Utility math functions."""

PI = 3.14159265

def circle_area(radius):
    """Return the area of a circle."""
    return PI * radius ** 2

def factorial(n):
    """Return n! recursively."""
    if n <= 1:
        return 1
    return n * factorial(n - 1)

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def magnitude(self):
        return (self.x**2 + self.y**2) ** 0.5

    def __repr__(self):
        return f"Vector2D({self.x}, {self.y})"
Python
# main.py - using the module
import mymath

print(mymath.PI)                    # 3.14159265
print(mymath.circle_area(5))        # 78.53981625
print(mymath.factorial(5))          # 120

v = mymath.Vector2D(3, 4)
print(v.magnitude())                # 5.0

# Or import specific names
from mymath import circle_area, Vector2D

print(circle_area(3))               # 28.27...
v2 = Vector2D(1, 1)
print(v2.magnitude())               # 1.414...

Packages

A package is a directory containing Python modules and an __init__.py file.

Shell
mypackage/
    __init__.py          # makes it a package; can be empty or have exports
    utils.py
    math/
        __init__.py
        geometry.py
        statistics.py
    io/
        __init__.py
        readers.py
        writers.py
Python
# mypackage/__init__.py
"""MyPackage - a collection of utility functions."""

# Re-export commonly used items at the package level
from .utils import helper_function
from .math.geometry import circle_area, Rectangle

__version__ = "1.0.0"
__author__  = "Alice"
Python
# Absolute imports (recommended)
import mypackage
from mypackage import circle_area                   # re-exported from __init__
from mypackage.math.geometry import Rectangle
from mypackage.io import readers

# Relative imports (inside a package)
# Inside mypackage/math/geometry.py:
# from ..utils import helper_function     # go up one level, then to utils
# from . import statistics                # same package
# from .statistics import mean, stdev    # specific names from sibling module

# Relative imports only work inside a package - not in top-level scripts

__name__ == "__main__"

Python
# script.py

def greet(name):
    return f"Hello, {name}!"

def main():
    import sys
    name = sys.argv[1] if len(sys.argv) > 1 else "World"
    print(greet(name))

# When run directly: python script.py Alice
# __name__ is "__main__" -> main() is called

# When imported: from script import greet
# __name__ is "script" -> main() is NOT called
if __name__ == "__main__":
    main()

# Check from another file
# import script
# print(script.__name__)    # "script" (not "__main__")
# print(script.greet("Bob"))    # Hello, Bob!

# Testing example - run tests only when executed directly
if __name__ == "__main__":
    # Quick smoke tests
    assert greet("Alice") == "Hello, Alice!"
    assert greet("World") == "Hello, World!"
    print("All tests passed")

__all__ and Exports

Python
# __all__ controls what 'from module import *' exports
# and signals the public API of the module

__all__ = ["PublicClass", "public_function"]    # explicit public API

class PublicClass:
    """This is part of the public API."""
    pass

def public_function():
    """This is part of the public API."""
    pass

def _private_function():    # leading _ means private by convention
    """Not exported by * and signals internal use."""
    pass

class _InternalHelper:      # won't be exported
    pass

# Without __all__, 'from module import *' imports all names
# that don't start with _

# Checking what a module exports
import mymodule
print(mymodule.__all__)     # ['PublicClass', 'public_function']

# Good practice: define __all__ in every module/package
# It serves as documentation and prevents accidental exports

Standard Library Modules

Python
# A tour of the most useful standard library modules

# os - operating system interface
import os
print(os.getcwd())                          # current directory
print(os.listdir("."))                      # directory contents
os.makedirs("path/to/dir", exist_ok=True)  # create dirs
print(os.environ.get("HOME", "/"))          # environment variables

# pathlib - modern file path handling (preferred over os.path)
from pathlib import Path
p = Path("/home/alice/projects")
print(p.name)           # projects
print(p.parent)         # /home/alice
print(p / "file.txt")   # /home/alice/projects/file.txt
print(p.exists())       # True/False

# sys - interpreter internals
import sys
print(sys.version)          # Python version
print(sys.argv)             # command-line arguments
print(sys.path[:3])         # import search paths

# datetime - dates and times
from datetime import datetime, date, timedelta
now   = datetime.now()
today = date.today()
tomorrow = today + timedelta(days=1)
print(now.strftime("%Y-%m-%d %H:%M"))   # 2024-06-15 14:30

# collections - specialized data structures
from collections import Counter, defaultdict, deque, OrderedDict, namedtuple
# (See Dictionaries tutorial for Counter and defaultdict)
dq = deque([1, 2, 3], maxlen=5)    # O(1) append/pop from both ends
dq.appendleft(0)
dq.append(4)
print(dq)   # deque([0, 1, 2, 3, 4], maxlen=5)

# itertools - combinatoric and lazy iterators
import itertools
print(list(itertools.chain([1, 2], [3, 4], [5])))   # [1, 2, 3, 4, 5]
print(list(itertools.islice(range(1000), 5)))         # [0, 1, 2, 3, 4]

# functools - higher-order functions
from functools import lru_cache, partial, reduce
double = partial(lambda x, y: x * y, 2)    # bind first arg to 2
print(double(5))    # 10

# re - regular expressions
import re
email_pattern = r"[\w.+-]+@[\w-]+\.[\w.]+"
emails = re.findall(email_pattern, "Contact alice@example.com or bob@test.org")
print(emails)   # ['alice@example.com', 'bob@test.org']

# json - JSON encoding/decoding (see JSON tutorial)
import json
data = json.dumps({"name": "Alice", "age": 30})
obj  = json.loads(data)

# random - random number generation
import random
print(random.randint(1, 6))         # dice roll
print(random.choice(["a", "b"]))    # random element
random.shuffle([1, 2, 3])           # in-place shuffle
ModuleKey Use
osOS operations, environment variables, file system
pathlibModern file path handling (prefer over os.path)
sysInterpreter info, argv, path, exit
datetimeDates, times, timedelta, formatting
collectionsCounter, defaultdict, deque, namedtuple
itertoolschain, combinations, permutations, groupby
functoolslru_cache, partial, reduce, wraps
reRegular expressions
jsonJSON encode/decode
mathsqrt, ceil, floor, log, trig, pi
randomRandom numbers, sampling, shuffling
typingType hints - Optional, Union, List, Dict

Frequently Asked Questions

import module imports the module as a namespace object - you access its contents with module.name. from module import name brings name directly into the current namespace. from module import * imports all public names (not recommended - pollutes namespace, hides origin). Use import module for clarity about where names come from. Use from ... import for frequently used names that would be verbose to prefix.

__init__.py marks a directory as a Python package and runs when the package is imported. In Python 3.3+, it is optional - "namespace packages" work without it. However, an explicit __init__.py is still recommended: it makes the package discoverable by older tools, lets you control what the package exports, and provides a place for package-level initialization. Keep it - even if empty.

When Python runs a file directly, it sets __name__ to "__main__". When the file is imported as a module, __name__ is the module's filename. The guard if __name__ == "__main__": lets you write code that only runs when the file is the entry point, not when imported. This is the standard pattern for making a module both importable as a library and runnable as a script.

Python searches in this order: (1) sys.modules cache - already-imported modules, (2) built-in modules (like sys, builtins), (3) frozen modules, (4) import path from sys.path. sys.path includes the script's directory (or current directory for interactive sessions), PYTHONPATH environment variable directories, and the installation's site-packages. You can inspect and modify sys.path at runtime.