Unit 3 · Classes, Modules & Files

Lesson · Unit 3 · 9 min read

Modules & imports, how Python code spreads across more than one file.

Anything bigger than a script lives in multiple files. A module is just a .py file you import. Here are the three import forms, the standard library you get for free, and the import habits that keep namespaces clean.

Section · 01

A module is a file

Anything you save as a .py file is a module. The file utils.py defines a module named utils. To use its contents from another file, import it.

# utils.py
def slugify(text):
    return text.lower().replace(" ", "-")

def truncate(text, length=80):
    if len(text) <= length:
        return text
    return text[:length - 1] + "…"

TITLE_PREFIX = "YorkSims · "
# main.py — in the same folder
import utils

print(utils.slugify("Hello World"))         # "hello-world"
print(utils.truncate("a very long...", 5))  # "a ve…"
print(utils.TITLE_PREFIX)                   # "YorkSims · "

That’s the entire mechanism. Functions, classes, variables defined at the top of a module are accessible from outside once someone imports the module.

Section · 02

The three import forms

Same module, three syntaxes:

1. import module

import utils

utils.slugify("Hello World")

Brings the whole module in under its own name. You access things as utils.something. Safest default — no name collisions, and a reader can see exactly where each name came from.

2. from module import name

from utils import slugify

slugify("Hello World")        # no prefix needed

Pulls a specific name into the current namespace. Shorter at the call site, but you lose the “where did this come from” context. Use when you call something often and the source is obvious from context.

3. import module as alias

import numpy as np
import pandas as pd

arr = np.array([1, 2, 3])
df = pd.DataFrame(...)

Useful for long module names. The community has standard aliases for big libraries (np for numpy, pd for pandas, pltfor matplotlib). Use the conventional alias when one exists; don’t invent new ones for your own modules unless they’re truly long.

The fourth form you should NOT use

from utils import *         # DON'T

Star-imports dump every name from the module into your namespace. Three problems with that:

1. If utils.py adds a new function tomorrow that happens to share a name
   with one of YOUR variables, the import silently overwrites yours.
2. A reader looking at your file can't tell where any name came from.
3. Linters can't catch unused imports, since they can't see what was imported.

The only place you’ll legitimately see star-imports is in__init__.pyfiles re-exporting a package’s public API. Even there, modern style avoids it.

Section · 03

What the standard library gives you

Python ships with a huge collection of modules — the standard library— for free. You don’t install anything; just import. A handful you’ll meet first:

import math                # sqrt, pi, log, sin, ...
import random              # randint, choice, shuffle, ...
import datetime            # date, datetime, timedelta
import json                # dumps, loads — turn data into JSON and back
import os                  # filesystem and environment
import sys                 # the interpreter itself (argv, exit, ...)
import re                  # regular expressions
import pathlib             # modern path manipulation
import collections         # Counter, defaultdict, deque, OrderedDict
import itertools           # chain, combinations, cycle, ...

Before you write something from scratch, check the standard library. Want to count occurrences of items in a list? collections.Counter already does it. Want to parse a date string? datetime.strptimealready does it. Knowing what’s there is half the skill of writing Python.

Small example

import json
from datetime import datetime

payload = {
    "event": "checkout",
    "amount_cents": 4995,
    "occurred_at": datetime.now().isoformat(),
}

print(json.dumps(payload, indent=2))

Section · 04

Third-party packages — pip and virtualenv

Everything beyond the standard library lives on PyPI (Python Package Index) and is installed with pip:

pip install requests

Then in your code:

import requests

response = requests.get("https://api.yorksims.com/health")
print(response.status_code)
print(response.json())

One critical habit: use a virtual environmentper project. A virtual environment is a sandboxed Python install with its own packages, so installing one project’s dependencies doesn’t break another’s.

# Create a virtualenv in the current folder
python -m venv .venv

# Activate it (Mac / Linux)
source .venv/bin/activate

# Activate it (Windows)
.venv\Scripts\activate

# Now pip installs only into this project
pip install requests

Modern alternatives — uv, poetry, pipenv— handle this for you. Pick one and stick with it. The one thing you must not do is install third-party packages into the system Python; that’s how you bork your OS.

Section · 05

When (and how) to split your code into modules

A single main.py is fine until it hits ~300 lines and you start having a hard time finding things. The split is driven by purpose, not size:

my_project/
├── main.py            # entry point: glues everything together
├── models.py          # the data classes
├── services.py        # business logic — what your program DOES
├── storage.py         # database / file persistence
├── notifications.py   # email / SMS / whatever
└── utils.py           # small, generic helpers used everywhere

The rule of thumb: each module should have one job that you can describe in a sentence. If you can’t — “models.py has the data classes AND the database access AND a date helper” — split it. Coherent modules are easier to read, easier to test in isolation, and easier to throw away when the requirements change.

The if __name__ == "__main__" guard

# greet.py
def greet(name):
    print(f"Hi, {name}.")

if __name__ == "__main__":
    greet("Ada")               # only runs when you do: python greet.py
                               # NOT when another file imports this module

This pattern lets a file be both an importable module AND a runnable script. The code under the guard runs only when the file is executed directly, not when it’s imported. Use it to put quick test calls or demos at the bottom of a module without polluting other code that imports it.

Curriculum source

Lesson content is original to YorkSims. Topic structure aligns with Python for Everybody by Dr. Charles R. Severance (py4e.com), licensed under Creative Commons Attribution 3.0 Unported.