Chapter 8 ยท Modules, I/O & Errors

Modules & Packages

Split code across files, import it in several ways, make a file that works both as a library and as a script, and group modules into packages.

You will learn

  • What a module is, and the different forms of import.
  • The if __name__ == "__main__": idiom.
  • Where Python looks for modules (sys.path), and what __pycache__ is.
  • Packages, __init__.py, relative imports, and __all__.
  • New in 3.15: lazy import for faster start-up.

What is a module?

A module is simply a .py file. Its name is the file name without .py. Everything defined at the top level of the file (functions, classes, variables) becomes an attribute of the module.

Analogy: a module is a toolbox with a label on it. import fibo brings the whole box onto your desk, and you take tools out with fibo.fib.

Start the REPL in the same folder and import it:

Each module has its own namespace (its own set of names). A variable called total inside fibo never clashes with your total. Inside a module, __name__ holds the module's name.

Forms of import

StatementWhat you can then writeNotes
import fibofibo.fib(10)the clearest; you always see where a name comes from
import fibo as fbfb.fib(10)a short alias, e.g. import numpy as np
from fibo import fib, fib2fib(10)copies just those names into your namespace
from fibo import fib as fibonaccifibonacci(10)renames on import
from fibo import *fib(10), fib2(10)imports all public names (no leading _); avoid in real code

โŒ Star import hides where names come from

โœ… Explicit is better than implicit

โ˜• Java: import in Python runs the module's code the first time (like a static initialiser). There is no classpath: Python searches a list of folders (see sys.path below). ๐ŸŸจ JS: from fibo import fib is like import { fib } from './fibo.js'. Python has no export keyword: every top-level name is importable.

A module's top-level code runs only once per process, the first time it is imported. Later imports reuse the cached module object stored in sys.modules. When you edit a module during a REPL session, either restart the REPL or use importlib.reload(fibo).

Executing modules as scripts

When you run a file directly (python3 fibo.py), its __name__ is set to "__main__" instead of "fibo". So you can add code that runs only when the file is run as a script, and not when it is imported:

๐Ÿ’ก Tip: the usual layout is to put the work in a main() function and call it from the guard. Then tests can import the file without side effects, and the file still works as a command.

The module search path

When you import spam, Python first checks for a built-in module with that name. Then it searches the folders listed in sys.path, in order:

  1. The folder containing the script you ran (or the current folder in the REPL).
  2. The folders in the PYTHONPATH environment variable, if it is set.
  3. The installation defaults: the standard library, then site-packages (where pip installs packages).

โš ๏ธ The shadowing pitfall. The script's own folder comes first. If you name your file random.py, json.py or email.py, then import random loads your file instead of the standard library's, and you get confusing errors like AttributeError: module 'random' has no attribute 'randint'. Never name your files after standard modules.

"Compiled" Python files

To speed up loading, Python caches a compiled version of each imported module in a __pycache__ folder, with names like fibo.cpython-315.pyc. The .pyc file holds bytecode: a lower-level form of your code that the interpreter runs.

๐Ÿ“ Changed in 3.15: the old module.__cached__ attribute is no longer set. Use module.__spec__.cached if you ever need the .pyc path.

Standard modules and dir()

Python ships with a large standard library of modules (Chapters 13โ€“14). Some, like sys, are built into the interpreter itself.

The built-in dir() lists the names a module defines. With no argument, it lists the names you have defined in the current scope.

Notice fib2 is still listed, even though the second version of fibo.py removed it. This REPL session imported the first version, and a module is loaded only once. Even importlib.reload() re-runs the file into the same module object, so old names stay behind. When in doubt, restart the REPL.

Packages

A package is a folder of modules, which gives you dotted names like sound.effects.echo. Mark a folder as a package by putting an __init__.py file in it. The file can be empty; its code runs when the package is first imported.

The three ways to import from it:

A real, runnable package

Here is a tiny shop package that you can create and run:

__all__ and import * from a package

from package import * does not scan the folder for submodules. It imports exactly the names listed in the package's __all__. Without __all__, it only imports the names already defined in __init__.py. Inside a plain module, __all__ limits what import * exports in the same way.

๐Ÿ“ Namespace packages: a folder without __init__.py can still be imported, as a "namespace package", which is used for splitting one package across several locations. Beginners should always add __init__.py; it avoids surprises.

Lazy imports (new in 3.15)

Large programs import many modules at start-up, even ones that are rarely used, and that slows down command-line tools. PEP 810 adds the soft keyword lazy. A lazy import binds the name immediately, but loads the module only the first time the name is used.

๐Ÿ“ Older pattern you'll still see: before 3.15, people delayed imports by putting the import statement inside the function that needed it. That still works and is still fine for one-off cases.

More examples

1. A config module shared by other files

2. Which file did a module come from?

3. Aliasing a long module name

4. Choosing an implementation at import time

(try/except is covered in Chapter 10.)

5. A package's public API through __init__.py

Recap

  • A module is a .py file. Prefer import module or from module import name, and avoid import *.
  • A module's code runs once, at its first import. if __name__ == "__main__": guards script-only code.
  • sys.path starts with the script's folder, so don't name files after standard modules.
  • A package is a folder with __init__.py. Inside it, use relative imports (from . import x), and run its modules with python3 -m pkg.mod.
  • lazy import x (3.15) delays loading until first use, and only works at the top level.

Exercises

Exercise 1 ๐ŸŸข easy build it ยท playground

Create playground/mathx.py with a function square(n). In playground/use_mathx.py, import it in two different ways and print square(7).

Show solution

Run python3 use_mathx.py from inside playground/. The script's own folder is the first entry in sys.path.

Exercise 2 ๐ŸŸข easy predict the output

greet.py contains print("loading greet") and nothing else. What does import greet; import greet print?

Show solution

loading greet, only once. The second import finds the module already in sys.modules and doesn't run it again.

Exercise 3 ๐ŸŸข easy explain why

Your file is called random.py and does import random; print(random.randint(1, 6)). It fails with AttributeError. Why?

Show solution

import random finds your random.py first, because the script's folder comes first in sys.path. Your file doesn't define randint. Rename the file and delete the __pycache__ entry for it.

Exercise 4 ๐ŸŸข easy fix the bug

Running tests.py, which does import calc, prints a large demo every time. Fix calc.py.

Show solution

When the file is imported, __name__ is "calc", so the demo is skipped.

Exercise 5 ๐ŸŸก medium predict the output

Given the shop package above, which of these work from the folder containing shop/? (a) python3 -m shop.cart (b) python3 shop/cart.py (c) python3 -c "from shop.cart import total; print(total([1]))"

Show solution

(a) and (c) work. (c) prints 1.2. (b) fails with ImportError: attempted relative import with no known parent package, because running a file by its path makes it __main__ with no package around it.

Exercise 6 ๐ŸŸก medium refactor it

Replace the star imports with explicit imports: from math import * and from statistics import *, where the code uses sqrt, pi and mean.

Show solution

Readers (and linters) can now see where each name comes from. It also avoids accidental clashes: both modules have names that could overlap.

Exercise 7 ๐ŸŸก medium build it ยท playground

Turn playground/money.py (Chapter 5) into a script and a module: importing it should print nothing, and python3 money.py 1234 should print โ‚ฌ12.34.

Show solution

Passing argv into main makes the function easy to test: you can call main(["x", "5"]) from a test.

Exercise 8 ๐ŸŸก medium explain why

Why is lazy import json inside a function a SyntaxError? What would you write instead?

Show solution

A plain import inside a function already runs only when the function is called, so it is lazy already. The lazy keyword exists to delay top-level imports, which normally run at start-up. Inside a function, just write import json.

Exercise 9 ๐Ÿ”ด hard predict the output

pkg/__init__.py contains print("init") and from .a import x. pkg/a.py contains print("a") and x = 1. pkg/b.py contains print("b"). What does python3 -c "import pkg.b" print?

Show solution

init, a, b, in that order. Importing pkg.b first imports pkg, which runs __init__.py, and that imports a. Then b runs.

Exercise 10 ๐Ÿ”ด hard mini-project ยท playground

Create a package playground/tracker/ with __init__.py, models.py (a function make_expense(amount, category) that returns a dict), report.py (a function total(expenses)) and __main__.py, so that python3 -m tracker (run from playground/) prints a small demo total. Re-export both functions in __init__.py and set __all__.

Show solution

A package's __main__.py is what python3 -m package runs. Chapter 16 grows this package into the full project.

Official docs