Skip to main content

Modules, Imports, and Entrypoints

What You'll Learn

How Python's module system works, how to import code, and how to structure a script so it runs correctly both directly and as an imported module.

What Is a Module?

A module is any .py file. When you import it, Python runs the file and makes its names available:

myapp/
├── main.py
├── utils.py
└── config.py

utils.py:

def format_name(name: str) -> str:
return name.strip().title()

TIMEOUT = 30

main.py:

import utils

name = utils.format_name(" alice ")
print(name) # Alice
print(utils.TIMEOUT) # 30

Import Styles

# Import the whole module — access with module.name
import os
import json
import pathlib

os.getcwd()
json.dumps({})

# Import specific names — use directly
from os.path import join, exists
from datetime import datetime, timedelta

join("/home", "alice") # no 'os.path.' prefix needed

# Import with alias — for long names
import numpy as np
import pandas as pd
from datetime import datetime as dt

np.array([1, 2, 3])

# Import all (avoid in production code — pollutes namespace)
from math import * # ❌ unclear where names come from

The Standard Library

Python ships with a huge standard library — no pip install needed:

# File system
import os
import pathlib
import shutil
import tempfile

# Data formats
import json
import csv
import configparser

# Dates and time
from datetime import datetime, date, timedelta
import time

# Text processing
import re
import string

# Math
import math
import random
import statistics

# System
import sys
import subprocess
import signal

# Networking
import urllib.request
import http.server

# Utilities
import itertools
import functools
import collections
import copy
import hashlib

Packages

A package is a directory containing an __init__.py file:

myapp/
├── __init__.py ← makes this a package
├── main.py
├── utils/
│ ├── __init__.py
│ ├── text.py
│ └── files.py
└── config.py

Importing from a package:

from myapp.utils.text import format_name
from myapp.utils import files
import myapp.config as cfg

The if __name__ == "__main__": Guard

This is crucial for any script that might also be imported:

# utils.py
def format_name(name: str) -> str:
return name.strip().title()

# This runs when you execute utils.py directly:
# python3 utils.py
# But NOT when another file imports it:
# import utils
if __name__ == "__main__":
test_name = " alice smith "
print(format_name(test_name)) # Alice Smith

Without this guard, running import utils in another file would also print to screen — unexpected side effect.

The main() Pattern

The standard way to structure a runnable script:

#!/usr/bin/env python3
"""Process user records and generate a report."""

import sys
import json
from pathlib import Path


def load_records(path: Path) -> list[dict]:
"""Load JSON records from a file."""
with open(path, encoding="utf-8") as f:
return json.load(f)


def filter_active(records: list[dict]) -> list[dict]:
"""Return only active records."""
return [r for r in records if r.get("active", False)]


def report(records: list[dict]) -> None:
"""Print a summary of records."""
print(f"Total records: {len(records)}")
for r in records:
print(f" - {r['name']}")


def main() -> int:
if len(sys.argv) < 2:
print("Usage: script.py <records.json>", file=sys.stderr)
return 1

path = Path(sys.argv[1])
records = load_records(path)
active = filter_active(records)
report(active)
return 0


if __name__ == "__main__":
sys.exit(main())

Benefits:

  • main() can be tested by calling it from tests
  • Clean entry point
  • Proper exit codes

Relative vs Absolute Imports

Inside a package, use relative imports to avoid hardcoding the package name:

# Absolute import (works anywhere)
from myapp.utils.text import format_name

# Relative import (only inside a package)
from .text import format_name # same package
from ..config import TIMEOUT # parent package

For scripts (not packages), always use absolute imports.

sys.path — Where Python Looks for Modules

import sys
print(sys.path) # list of directories Python searches for modules

Python searches in order:

  1. Current directory
  2. PYTHONPATH environment variable
  3. Standard library
  4. Site packages (pip installs)

Add a path at runtime:

sys.path.insert(0, "/path/to/my/modules") # use sparingly

Common Mistakes

MistakeFix
from module import *Import specific names only
Code at module level with side effectsGuard with if __name__ == "__main__":
Circular importsRestructure — put shared code in a third module
Shadowing stdlib namesDon't create json.py, os.py, etc.

Quick Reference

# Import
import module
from module import name
from module import name as alias
import module as alias

# Entrypoint
def main() -> int:
...
return 0

if __name__ == "__main__":
import sys
sys.exit(main())

# Standard library highlights
import os, sys, json, re, math, random
import pathlib, shutil, subprocess
from datetime import datetime, timedelta
from collections import defaultdict, Counter
from itertools import chain, islice
from functools import lru_cache, partial

What's Next

Lesson 3: Objects, Dataclasses, and Types