PCAP — Modules and Packages — what import actually does
The mechanics behind the shape. Walk import in order and every packaging question resolves.
Import is four steps: check cache, walk path, execute, bind. Every "why doesn't my import work" question resolves on one of the four.
§ IFrame
The PCAP exam tests the mechanics of Python's module system as tightly as it tested the mechanics of exceptions six days ago. Not the philosophy of when to reach for a package, but the exact behavior of import, the role of __init__.py, the difference between import x and from x import y, the meaning of the search path, and enough of the standard library that you can answer "which module does that" without opening a browser.
This is the PCAP-emphasis face of today's trio. The Ops lesson today built the operational shape a fleet library takes. The Dev lesson went into the packaging metadata that describes the shape. This lesson drills the mechanics that both other lessons assumed, because those mechanics are exactly what the exam tests, and because the mechanics change slightly when you cross from a single-file script into an importable package.
§ IIDomain Foundations — What import Actually Does
import spam in a Python source file does four things in order. First, Python checks sys.modules to see if spam has already been imported this run; if yes, it binds the cached module object to the name spam in the current namespace and does not re-execute the module's code. Second, if not cached, Python walks sys.path looking for a spam.py file or a spam/ directory. Third, when found, Python loads and executes the module's code top-to-bottom, populating the module object with the names defined there. Fourth, the module is stored in sys.modules and bound in the current namespace.
The exam asks precisely these mechanics because they explain every "why doesn't my import work" question. Order and caching are the structural facts: a module is executed exactly once per interpreter session (barring importlib.reload), and every subsequent import of the same name binds the same cached object. Two files that both import spam see the same module state.
The three import forms produce different bindings but the same underlying work.
import spam binds the name spam in the current namespace to the module object. Access members with dot: spam.foo(). from spam import foo does the full import spam machinery, then binds only the name foo (the value of spam.foo) in the current namespace. spam itself is not bound; foo is. from spam import * does import spam, then binds every public name (respecting spam.__all__ if defined; otherwise every name not starting with _) into the current namespace. Prefer avoidance; the exam expects you to know it works but never recommends it.
§ IIIDomain Foundations — Packages and __init__.py
A package is a directory containing an __init__.py. import spam.eggs first imports the package spam (which runs spam/__init__.py), then imports the submodule eggs from within it (which runs spam/eggs.py). Both are executed exactly once, and both end up in sys.modules with dotted names: sys.modules["spam"] and sys.modules["spam.eggs"].
Lutz's package chapter that grounds this lesson describes three canonical uses for __init__.py. The first is to mark the directory as a regular package (a role now optional under PEP 420 namespace packages, but still required if you want the traditional shape and the sub-imports it enables). The second is to run package-level initialization: setting __version__, configuring logging, warming caches. The third is to define the public API: from .eggs import breakfast in __init__.py promotes breakfast to a top-level attribute of spam, callable as spam.breakfast() without knowing that breakfast actually lives in spam.eggs.
The exam distinguishes regular and namespace packages precisely because the difference produces different runtime behavior. A regular package has an __init__.py; imports of the package always run that file first. A namespace package (introduced by PEP 420) is a directory without __init__.py that Python treats as a package only through the joint contribution of multiple sys.path entries; imports run no initialization code, and multiple installed distributions can contribute submodules to the same top-level name. Know which one you have because the debugging is different.
Relative imports appear inside packages: from . import sibling imports a sibling module in the same package; from .. import cousin imports from the parent package; from .subpackage import module imports from a child. Relative imports use dots to walk the package hierarchy and only work inside a package (a module run as python script.py at the shell has no package context, and relative imports raise ImportError).
§ IVDomain Foundations — sys.path, Search Order, and __main__
sys.path is a list of directory strings Python searches when resolving import names. Its contents at interpreter start are, in order: the directory of the script being run (or empty string for interactive mode, which means current directory), the entries of the PYTHONPATH environment variable, and the interpreter's default site-packages directories. sys.path is mutable at runtime; a module can append to it and change what future imports see.
The exam presents scripts and asks which of several spam.py files would be imported. You answer by walking sys.path in order and returning the first match. This is the mechanic that makes python script.py from a directory with a local math.py shadow the standard-library math module, which is a common newbie failure the exam expects you to diagnose.
Every executed Python script has a special attribute: __name__. When a file is run directly (python spam.py), its __name__ is the string "__main__". When a file is imported (import spam), its __name__ is "spam". This is the mechanic behind the ubiquitous if __name__ == "__main__": block: code inside it runs only when the file is executed directly, not when it is imported. The exam asks the mechanic directly; know both cases and know why the guard exists (so a module can offer both an importable library surface and a runnable CLI without one contaminating the other).
§ VDomain Foundations — The Standard Library Tour
The PCAP objective includes a "familiarity with the standard library" component that tests whether you can name the module for a given task. The exam does not ask every module; it asks a common core. Know each of the following at least by purpose and one canonical call.
math — mathematical constants and functions: math.pi, math.sqrt(x), math.floor(x), math.ceil(x). Real numbers only; complex numbers live in cmath. random — pseudo-random numbers: random.random() for [0,1), random.randint(a, b) for integers inclusive on both ends, random.choice(seq), random.shuffle(seq), random.seed(n) for reproducibility. os — operating-system interface: os.getcwd(), os.listdir(path), os.environ (a dict of environment variables), os.path.join(a, b) (though pathlib.Path is now recommended). sys — interpreter interface: sys.argv (command-line arguments), sys.path, sys.stdin/stdout/stderr, sys.exit(code). datetime — date and time: datetime.date.today(), datetime.datetime.now(), datetime.timedelta(days=7), strftime and strptime for formatting. time — lower-level clock and sleep: time.time() returns Unix timestamp float, time.sleep(seconds) blocks. json — JSON serialization: json.dumps(obj) to string, json.loads(text) from string; json.dump(obj, file) and json.load(file) for file objects. re — regular expressions: re.match, re.search, re.findall, re.sub.
Know the difference between random.randint(1, 6) (inclusive on both ends: 1 through 6) and random.randrange(1, 6) (exclusive on the right: 1 through 5). The exam has been known to turn on this.
§ VIConnection to Today's Ops and Dev Lessons
The three lessons today are one topic at three altitudes. This PCAP lesson holds the mechanics: what import does, when __init__.py runs, how sys.path resolves. The Ops lesson took those mechanics and made a shape from them: a fleet library structured for reuse, exposing CLI tools through entry points and plugin surfaces through group discovery. The Dev lesson took the shape and named the file that describes it to Python's packaging machinery. If you can walk the import order in your sleep and recite the four steps import performs, both other lessons read as natural extensions rather than new material.
§ VIIPractice Questions
main.py runs import spam twice on consecutive lines. How many times is the code inside spam.py executed?from math import sqrt, is the name math bound? Is sqrt?pkg has __init__.py and sub.py. A script runs import pkg.sub. In what order are the files executed?__name__ Behaviorspam.py prints __name__. What prints when run as python spam.py? What prints when another file import spams it?randint, and what does random.randint(1, 6) return?p has a.py and subpackage q/ (with q/b.py). From inside q/b.py, write the correct relative import to reach p.a.§ VIIIQuestion Resolutions
Answer 1. Exactly once. The first import spam runs spam.py top-to-bottom and stores the resulting module object in sys.modules["spam"]. The second import spam finds spam already in sys.modules and binds the cached object without re-executing the file. Modules are executed exactly once per interpreter session; the language guarantee is what makes module-level state (like class definitions, module-level variables, and expensive imports) safe to write.
Answer 2. Only sqrt is bound. from math import sqrt executes import math internally to populate sys.modules["math"], then binds only the name sqrt (the value of math.sqrt) into the current namespace. The name math is not bound; typing math.pi in the current namespace would raise NameError. Use import math if you want the module name available, or from math import sqrt, pi to bind multiple names explicitly.
Answer 3. First pkg/__init__.py runs (executing the package initialization code and populating sys.modules["pkg"]), then pkg/sub.py runs (populating sys.modules["pkg.sub"]). Any name bound in __init__.py is available on the package object; any name bound in sub.py is available on pkg.sub. The package's __init__.py always runs first when any of its submodules is imported.
Answer 4. When run as python spam.py, the file prints __main__ because Python sets __name__ to that string for the file being executed directly. When another file runs import spam, the file prints spam because Python sets __name__ to the module's dotted name (or top-level name for un-nested modules). This dual behavior is what makes if __name__ == "__main__": a useful guard for CLI-vs-library code.
Answer 5. The random module provides randint. random.randint(1, 6) returns a random integer from 1 through 6 inclusive on both ends (that is, one of {1, 2, 3, 4, 5, 6} chosen uniformly at random). Contrast random.randrange(1, 6) which is exclusive on the right and returns one of {1, 2, 3, 4, 5}.
Answer 6. from .. import a (from the parent package, import a). Written from inside q/b.py, one dot means "the current package" (q) and two dots mean "the parent package" (p), so from .. refers to p, and import a imports p.a. Alternatively from p import a also works but is an absolute import; the exam distinguishes the two and prefers relative imports for intra-package references.
Filed 2026-07-30 backfill (Thu 2026-07-30 Fajr slot, in-cycle) · Cert lesson · Python deep-mastery track (day 8, visit 3; PCAP-emphasis per python_day_cert_counter=2)