Modules, Packages, and Standard Library
Beginner⏱55 minutes📖16 min readLesson 10 of 10

Modules, Packages, and Standard Library

Organise Python code into modules and packages, follow the import system through sys.modules and sys.path, and use the standard library with confidence

On a Friday afternoon, an analyst saves a small file called random.py in the project folder. The file holds one function that rolls a die for a lunchtime game. On Monday, the nightly report stops with AttributeError: module 'random' has no attribute 'randint'.

The standard library was never missing. Python found the analyst's file first, because the script directory sits at the front of the search path. A local name had quietly replaced a module that ships with every Python installation.

An import is a request for a named piece of code, and Python answers it by searching a list of locations in a fixed order. That search explains the Monday failure, and it also explains why a well named file can save a project from the same fate.

This lesson is the last stop of the beginner course. You will learn how modules and packages are found, how names bind to them, and how the standard library covers the everyday work of a small program.

Key terms

Module
A Python source file that the import system can load as a unit, with its own namespace of names.
Package
A directory that groups modules under one import name, usually with an __init__.py file.
Import
A statement that asks Python to find a module, load it if needed, and bind a name in the current namespace.
Module object
The object that holds a loaded module's attributes, such as its functions, classes and constants.
Wildcard import
The form from module import star, which binds every public name from a module into the current namespace.
Main guard
The test if __name__ == "__main__", which runs a block only when the file is executed directly.
sys.path
The ordered list of directories that the import system searches for a module name.
Namespace package
A package without an __init__.py file, defined by PEP 420, whose portions can come from several directories.
Bytecode cache
The __pycache__ directory, which stores compiled bytecode so later imports skip part of the compile step.
Standard library
The collection of modules that ships with every normal Python installation and needs no separate download.

1. The Import That Found the Wrong File

Start with two files in one folder, because the failure needs only that much. The analyst's file is named after the module it wanted to use.

python
# random.py, saved by mistake in the project folder def roll(): return 4
python
# report.py import random print(random.randint(1, 6))

Running python report.py raises AttributeError, because import random binds the local file and never reaches the standard library. The name in the statement is correct, and the file on disk is wrong.

A module is a .py file that Python loads as a unit, and its import name comes from the file name without the suffix. The import statement asks for that name, and the interpreter decides which file answers.

The first entry on the search list is the directory of the script that is running. A file in that directory can therefore hide a module with the same name, a behaviour called shadowing. Section 11 returns to this failure with a repair.

📌Important

An import failure is often a name resolution problem, not a missing installation. Read the module name and the first search location before you reinstall anything.

2. Modules Are Files, Imports Are Lookups

A module is a .py file, and importing it runs the file once from top to bottom. The run produces a module object, and the name on the left of the import binds to that object.

python
# helper.py print("loading helper") VALUE = 10 def greet(name): return f"Hello, {name}!"
python
# main.py import helper print(helper.VALUE) print(helper.greet("Ada"))

The first line of output is loading helper, because the import runs the file. The calls then read VALUE and greet from the module object, using a dot to reach each member.

The dot is not decoration. helper names the module, and VALUE names an attribute inside it, so the full expression walks from the module to the value. A bare VALUE in main.py would raise NameError, because the import bound only the module name.

The diagram follows one import from the statement to the bound name. The cache check happens before any file is read.

100%
Description

A flowchart follows an import statement from the script through the finder and the sys.modules cache, ending at a cached module or a fresh load.

A cache hit skips the loader and returns the object that is already in memory. A cache miss reads the source, builds the module object, and stores it under its import name. Both paths end at the same bound name in the calling script.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A file helper.py contains print("loading helper") and VALUE = 10 at module level. A script runs import helper twice in a row. How many times does loading helper appear?

3. Two Import Forms, Two Kinds of Name

import helper binds the module, and from helper import VALUE binds the value itself. The difference looks small, and it decides whether a later change to the module can reach the imported name.

python
# helper.py VALUE = 10
python
# main.py import helper from helper import VALUE helper.VALUE = 20 print(helper.VALUE) # 20 print(VALUE) # 10

The module attribute changed, and the imported name did not. from helper import VALUE took a reference to the integer at import time, and rebinding the module attribute later creates a new integer. The old reference keeps the old object.

This is a common source of stale values in long-running programs. A configuration module that is reloaded or patched will not update names that were imported with from. Import the module when the value may change, and import the name when it is a stable constant.

Form
Name bound
Later module change visible
import helper
module object
yes, through helper.VALUE
from helper import VALUE
the value
no, the name keeps the old object
import helper as h
module object under h
yes, through h.VALUE
from helper import VALUE as v
the value under v
no, the name keeps the old object

PEP 8 asks for imports at the top of the file, one per line. That placement makes dependencies visible before any code runs, which shortens the search during review.

Matching Question
How to answer

Matching exercise with 4 pairs. Match each item on the left with its answer on the right.

Match each import form to the name it binds in the current namespace.

import helper
Drop here
from helper import VALUE
Drop here
import helper as h
Drop here
from helper import greet as g
Drop here

Drag answers from here:

the module object helper
the value VALUE
the module object under h
the function under g
Practice Question
How to answer

Multiple-choice question with 2 options. Select one option, then choose Check Answer.

A script runs from helper import VALUE, and a later line sets helper.VALUE = 20. The name VALUE in the script still refers to the old integer 10.

4. Aliases and the Module Object

An alias binds the same object under a second name. import helper as h does not copy the module, and h is helper is True when both names exist in the same program.

python
import helper as h from helper import greet as hello print(h.VALUE) print(hello("Ada")) print(h.greet is hello) # True

An alias is useful when the module name is long, or when it collides with a local name. Many libraries publish a short conventional alias, and the reader learns it once. The alias changes the spelling and never changes the object.

The from ... import ... as ... form aliases a member, which is handy when two modules export the same function name. The two names still point at separate functions, so the alias prevents a silent overwrite.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A script contains import helper as h and also import helper. Which statement about the two names is correct?

5. Wildcard Imports and the Names You Cannot See

from helper import * binds every public name from the module into the current namespace. Public means every name that does not start with an underscore, unless the module defines __all__.

python
# tools.py def total(values): return sum(values) def mean(values): return sum(values) / len(values)
python
# report.py total = 0 from tools import * print(total([1, 2, 3]))

The script assigns 0 to total, and the wildcard import then replaces that name with the function. The reader sees a number on one line and a callable on the next, with no assignment in between. PEP 8 asks authors to avoid wildcard imports for exactly this reason.

A module can define __all__ to list the names a wildcard import should expose. That list is a public contract, and it also keeps helper names out of the caller's namespace.

python
# tools.py __all__ = ["total", "mean"] def total(values): return sum(values) def mean(values): return sum(values) / len(values) def _clean(values): return [value for value in values if value is not None]

__all__ limits the wildcard form, and it does not affect a direct import. from tools import _clean still works, because the underscore is a convention and not a lock. Treat the list as documentation for the public surface.

⚠️Warning

A wildcard import can replace a name that already exists in the file, and nothing in the line shows which name changed. Use it only in a short experiment, never in shared code.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A script sets total = 0, then runs from tools import *, and tools defines a function named total. What happens to the name total in the script?

6. The Cache in sys.modules

The first import of a module runs the file and stores the module object in sys.modules, a dictionary keyed by module name. Every later import of that name returns the cached object, so top-level code runs once per process.

100%
Description

A sequence diagram shows a script asking the import machinery for a module, the cache miss, the module load, and the cached result on a second import.

importlib.reload(helper) reruns the file and updates the existing module object. The reload is explicit, and plain imports never repeat the top-level work. A long-running REPL therefore keeps the old code until you reload or restart.

Module-level state is shared by every importer, because they all hold the same object. A list defined at the top of a module is one list, and an append from one caller is visible to the next. Prefer functions and explicit arguments when state must stay local.

ℹ️Note

📚 Did you know? The __pycache__ folder holds compiled bytecode for faster later imports. It is a cache, not source, so it is safe to delete and it never changes what a module does.

Fill in the Blanks
How to answer

Fill-in-the-blank exercise with 1 blanks. Type the missing words, then check your answer.

Complete the sentence about the module cache.

The first import runs a module and stores the module object in blank, so a later import reuses that object without rerunning the file.

Word Bank, drag words into the blanks above:

sys.modules
sys.path
__pycache__

7. __name__ and the Main Guard

Every module has a __name__ attribute. During an import it holds the module name, such as "helper", and when the file runs as the main program it holds "__main__".

python
# calculator.py def add(a, b): return a + b def subtract(a, b): return a - b if __name__ == "__main__": print(add(3, 5)) print(subtract(10, 4))

python calculator.py sets __name__ to "__main__", so the test block runs. import calculator sets __name__ to "calculator", so the block is skipped and only the two functions are defined.

The guard keeps test code, demo output and command-line entry points out of the import path. A library that prints a banner at import time annoys every caller, and the guard is the standard repair.

The state diagram shows the two lives of one file. The left path is an import, and the lower path is a direct run.

100%
Description

A state diagram traces a module from source file to loaded module, then to imported reuse, with a separate branch for running as the main program.

The guard is a condition on the name, not a special syntax. Any code outside the block runs in both cases, which is why definitions stay at module level and test code moves inside the guard.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A file app.py ends with if __name__ == "__main__": print("demo"). A second program runs import app. What does the second program see?

💡Tip

Run python -c "import calculator; print(calculator.__name__)" to see the import name. The same one-line check with __file__ shows which file answered an import.

8. Packages and __init__.py

A package is a directory that groups modules under one name. A regular package contains an __init__.py file, which runs once when the package is first imported. The file may be empty, and it may re-export names for a simpler public interface.

plaintext
project/ ├── main.py └── shapes/ ├── __init__.py ├── circle.py └── rectangle.py
python
# shapes/circle.py from math import pi def area(radius): return pi * radius ** 2
python
# main.py from shapes import circle print(circle.area(5)) # 78.53981633974483

from shapes import circle loads the package first, then the module inside it. The package has its own namespace, and the module has a second namespace beneath that. A dotted name follows the same path in reverse, from the package to the member.

python
# shapes/__init__.py from shapes.circle import area as circle_area __all__ = ["circle_area"]

An __init__.py file can re-export a name, so callers write from shapes import circle_area instead of the longer path. The file is ordinary Python, and it runs once at package import. Keep it small, because every importer pays for its top-level work.

Unit
Shape on disk
Import name
Typical job
Module
one .py file
helper
a group of related functions
Regular package
directory with __init__.py
shapes
a group of modules
Namespace package
directory without __init__.py
plugins
portions merged from several locations

9. Namespace Packages

A namespace package is a directory without __init__.py. PEP 420 lets Python treat such a directory as a package, and it can combine portions of the same package found on different path entries.

The feature is useful for plugins that ship in separate distributions under one shared name. It is rarely the right choice for a beginner project, because the package boundary becomes implicit. Create __init__.py for ordinary packages, and use a namespace package only when separate installs must merge.

python
# A namespace package can look like this, with no __init__.py # plugins/ # csv_export.py # json_export.py

The import statement is identical in both cases, so a reader cannot tell from the call whether a package has an init file. The difference appears on disk, and it matters when two installs contribute files to one namespace.

Practice Question
How to answer

Multiple-choice question with 2 options. Select one option, then choose Check Answer.

Every directory that Python can import as a package must contain an __init__.py file.

ℹ️Note

📚 Did you know? PEP 420 made __init__.py optional for namespace packages. Most projects still create the file, because an explicit boundary is easier to read and easier to debug.

10. Relative Imports, sys.path and python -m

Inside a package, a relative import starts with a dot. from . import core imports a sibling module, and from .core import VALUE imports a name from that sibling. The dot means the package that contains this file, and two dots mean the parent package.

python
# shapes/report.py from .circle import area def describe(radius): return f"Area: {area(radius):.2f}" if __name__ == "__main__": print(describe(5))

python shapes/report.py runs the file as a top-level script, so there is no package context and the relative import fails with ImportError. python -m shapes.report runs the module with package context, and the same import works.

sys.path is the list of locations the import system searches. The first entry is the script directory, or the current directory for -c and the REPL. Then come PYTHONPATH entries and the installation's site-packages.

bash
python shapes/report.py # ImportError, no package context python -m shapes.report # Area: 78.54
🚨Danger

A relative import error is a context error, not a missing file. Check how the file was launched before you move the module or edit the import.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A module uses from .circle import area, and running python shapes/report.py raises ImportError. Which command runs the module with package context?

11. Shadowing the Standard Library

Return to the Monday failure. A local random.py wins because the script directory is first on sys.path, and the standard library lives later in the list. The interpreter has no way to know that the local file was a mistake.

bash
python -c "import random; print(random.__file__)"

This one line prints the file that answered the import. If the path points into your project, you have found the shadow. Rename the file, delete the old __pycache__ entry, and run the check again.

Names such as random.py, json.py and types.py are the common offenders, and the same rule covers any standard library name. Name a project file after the job it does, such as dice_game.py or report_helpers.py, and the collision disappears.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A project folder contains random.py, and report.py in the same folder runs import random. Which file does the import load?

12. A Standard Library Tour: Numbers, Chance and Time

The standard library is the set of modules that ship with Python. It covers arithmetic, statistics, random choice, dates and files, so a beginner can solve most small tasks without installing anything. The diagram groups the modules by the kind of job they do.

100%
Description

A flowchart groups standard library modules by task, placing math and statistics with numbers, random with simulation, secrets with security, and pathlib and json with data.

Math

python
import math print(math.sqrt(16)) # 4.0 print(math.pi) # 3.141592653589793 print(math.floor(-3.2)) # -4 print(math.ceil(-3.2)) # -3 print(math.isclose(0.1 + 0.2, 0.3)) # True

floor moves down and ceil moves up, including for negative numbers. isclose compares floats with a tolerance, which is safer than an equality test after arithmetic.

Statistics

python
import statistics scores = [1, 2, 3, 4] print(statistics.mean(scores)) # 2.5 print(statistics.median(scores)) # 2.5 sample = [2, 4, 4, 4, 5, 5, 7, 9] print(statistics.pstdev(sample)) # 2.0

The mean adds and divides, the median sorts and takes the middle, and the standard deviation measures spread. pstdev treats the list as a full population, while stdev estimates from a sample.

Random and secrets

python
import random import secrets print(random.randint(1, 10)) # 1 through 10 inclusive print(random.choice(["tea", "coffee"])) print(secrets.token_urlsafe(16)) # a URL-safe token

random is a simulation tool, and its output is predictable enough for games and sampling. It is not suitable for passwords, reset tokens or anything an attacker must not guess. secrets is the standard library module for values that need real unpredictability.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program builds a password reset token with random.randint and sends it to a user. Why is that a poor choice?

Datetime

python
from datetime import date, datetime, timedelta, timezone today = date(2026, 11, 5) print(today) # 2026-11-05 now = datetime.now(timezone.utc) print(now.isoformat()) delivery = today + timedelta(days=3) print(delivery) # 2026-11-08

date holds a calendar day, datetime adds a clock time, and timedelta holds a duration. A naive datetime has no timezone, and an aware datetime carries one. Aware values are safer when a program serves several regions.

ℹ️Note

📚 Did you know? datetime.now() returns a naive value with no timezone attached. Pass timezone.utc when the value will travel between machines or regions.

13. A Standard Library Tour: Files, JSON, CSV and Collections

pathlib

python
from pathlib import Path data_dir = Path("data") data_dir.mkdir(parents=True, exist_ok=True) report = data_dir / "sales.csv" report.write_text("region,amount\nnorth,120\n", encoding="utf-8") print(report.exists()) # True print(report.read_text(encoding="utf-8"))

The slash operator joins path parts without string concatenation, and mkdir(parents=True, exist_ok=True) is safe to rerun. read_text and write_text handle the open and close for a small file.

python
for path in Path("data").glob("*.csv"): print(path.name)

JSON

python
import json record = {"name": "Ada", "score": 92} text = json.dumps(record) print(text) # {"name": "Ada", "score": 92} print(json.loads(text)["score"]) # 92

dumps turns a Python object into JSON text, and loads parses JSON text back into Python objects. dump and load do the same work with a file object, which avoids building a large string in memory.

CSV

python
import csv with open("data/sales.csv", newline="", encoding="utf-8") as handle: rows = list(csv.DictReader(handle)) print(rows[0]["region"]) # north print(sum(float(row["amount"]) for row in rows)) # 120.0

csv.reader returns each row as a list, and csv.DictReader uses the header row as keys. Every value arrives as a string, so a numeric column needs an explicit conversion.

Collections

python
from collections import Counter, defaultdict, deque votes = ["tea", "coffee", "tea", "juice", "tea"] print(Counter(votes).most_common(1)) # [('tea', 3)] by_region = defaultdict(list) by_region["north"].append(120) print(dict(by_region)) # {'north': [120]} queue = deque([1, 2, 3]) queue.appendleft(0) print(queue) # deque([0, 1, 2, 3])

Counter counts values, defaultdict creates a missing value on demand, and deque is a double-ended queue. The namedtuple form builds a small record type, which is useful when a tuple needs field names.

Matching Question
How to answer

Matching exercise with 5 pairs. Match each item on the left with its answer on the right.

Match each standard library module to the task it handles best.

statistics
Drop here
secrets
Drop here
pathlib
Drop here
json
Drop here
collections
Drop here

Drag answers from here:

mean, median and standard deviation
password reset tokens
joining and reading file paths
converting a dictionary to text
counting values and building queues

14. Third-Party Packages and the Environment Rule

The standard library stops at general purpose tools. A third-party package adds a focused capability, such as an HTTP client or a data frame, and it is installed from PyPI. Lesson 02 covers the full toolchain, including pip, uv and virtual environments.

bash
python -m venv .venv source .venv/bin/activate python -m pip install requests

An isolated environment keeps a project's packages away from the system interpreter, so two projects can use different versions. uv offers the same isolation with faster installs and a lock file, and lesson 02 compares the two workflows.

Standard library
Third-party package
Install
ships with Python
installed from PyPI
Version
tied to the interpreter
pinned per project
Review
stable and documented
check the project health
Example
json
requests

Importing a third-party module looks identical to importing a standard one, because the import system does not care who wrote the file. What changes is the environment that supplies it, which is why the install step belongs in the project setup.

📌Important

Install project packages into a virtual environment, not into the system interpreter. The environment is part of the project, and it should be reproducible on another machine.

15. Imports in the Python 3.15 World

2026 Updates (as of November 2026)

ℹ️Note

What changed recently, and why older tutorials may look different.

  • Python 3.15.2 is the current stable release (3.15.0 shipped on 2026-10-09, and 3.15.2 followed on 2026-10-25).
  • Python 3.10 reached end of life on 2026-10-01. Every supported series today includes packages, namespace packages and the main guard.
  • The import rules in this lesson are unchanged in 3.15. The module cache, sys.path, relative imports and python -m behave as described.

The practical advice is stable: keep imports at the top, avoid wildcard forms, and run package modules with -m. The version note matters because an older tutorial may show a layout that a new project no longer needs.

Real-World Case Studies

The dice file that broke a report. A local random.py shadowed the standard library module, and the repair was a rename plus a check of random.__file__. The incident cost a morning, and the root cause was a file name rather than a missing package.

A nightly sales report. A clinic exports one CSV file each evening, and a script reads it with csv.DictReader, counts regions with Counter, and writes a summary with json.dumps. The main guard keeps the report out of the import path when another script reuses its functions.

A static preview on a laptop. python -m http.server 8000 serves the current directory, which is useful for a quick local preview. The same command exposes the folder to anyone who can reach the machine, so it belongs on a trusted network only.

ℹ️Note

📚 Did you know? The standard library is itself a package collection, and python -m http.server runs a module inside it. The -m flag and the package layout you learned here are the same mechanism at work.

Practice Questions

Answer these before the next lesson; the final two questions reach back to earlier lessons. Each stem stands alone, so you can answer it without the surrounding prose.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A module helper.py defines def add_item(item, cart=[]): cart.append(item); return cart. A script calls helper.add_item("lamp") and then helper.add_item("kettle"). What does the second call return?

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A CSV file has a header row with amount, then rows 10 and 20. A script runs rows = list(csv.DictReader(handle)) and then sum(float(row["amount"]) for row in rows). What is the result?

What Comes Next

You can now split a program into modules, group them into a package, and explain why an import found one file instead of another. You can also reach for the standard library before you reach for an install command.

The next course, python-intermediate, builds on this base with classes, exceptions, file input and output, and testing. Lesson 02 of this course remains your reference for pip, uv and virtual environments when a project needs a third-party package.

Before moving on, the video below is a full beginner course that revisits modules near the end. It is a useful second voice for the same ideas when a written explanation needs a replay.

A long-form beginner course with sections on modules, packages and the standard libraryWatch on YouTube
Description

A long beginner video course whose modules and packages chapters replay the import, package layout and standard library material from this lesson.

✅Success

Key Takeaways:

  1. A module is a .py file, and the first import runs it once and stores the module object in sys.modules.
  2. import helper binds the module, while from helper import VALUE binds the value and does not follow later reassignments.
  3. An alias such as import helper as h is another name for the same module object, not a copy.
  4. A wildcard import can overwrite existing names, so PEP 8 asks authors to avoid it in shared code.
  5. __name__ is the import name, or "__main__" when the file runs directly, and the main guard keeps test code out of the import path.
  6. A regular package is a directory with __init__.py, while a namespace package omits the file under PEP 420.
  7. Relative imports need package context, so run a package module with python -m package.module instead of a file path.
  8. sys.path starts with the script directory, which is why a local random.py or json.py can shadow the standard library.
  9. math, statistics, secrets, datetime, pathlib, json, csv and collections cover most beginner tasks without an install.
  10. Third-party packages belong in an isolated environment, and lesson 02 covers pip, uv and virtual environments in full.

References

Every factual claim links to its source, with the access date printed so the source can be rechecked.

  1. [1]ModulesDocumentation

    Python Software Foundation · Accessed 2026-11-05

  2. [2]The import systemDocumentation

    Python Software Foundation · Accessed 2026-11-05

  3. Python Software Foundation · Accessed 2026-11-05

  4. Python Software Foundation · Accessed 2026-11-05

  5. Python Software Foundation · Accessed 2026-11-05

  6. Python Software Foundation · Accessed 2026-11-05

  7. Python Software Foundation · Accessed 2026-11-05

  8. Python Software Foundation · Accessed 2026-11-05

  9. Python Software Foundation · Accessed 2026-11-05

Progress100%