Overview
Modules and Packages is Section 1 of the PCAP-31-03 syllabus and has five objectives. The first covers the import variants (import, from ... import, import ... as and from ... import *), qualified names for nested modules, the dir() function and the sys.path list. The next three each target one standard library module: math for ceil(), floor(), trunc(), factorial(), hypot() and sqrt(); random for random(), seed(), choice() and sample(); and platform for functions such as system(), machine(), processor(), version(), python_implementation() and python_version_tuple(). The fifth objective covers writing your own modules and packages, including the __pycache__ directory, the __name__ variable, private names, the __init__.py file and how packages map to directory trees.
Exam status: PCAP-31-03 is the active version. The Python Institute lists PCAP-31-04 as pending release, scheduled for October 31, 2026, and describes it as a routine update; its syllabus was not yet published when this page was reviewed.
How it's tested
PCAP items in this section usually show a short snippet and ask what it prints, which line raises an error, or which import statement makes a later line work. Some items list several statements and ask you to select every valid one, so precise knowledge of name binding after each import form matters more than memorized definitions.
-
Predict the output
A snippet calls math or platform functions, and you choose the exact printed values, including type details such as int versus float.
-
Fix the import
A later line uses a name, and you pick the import statement that makes the name available without an error.
-
Read the package tree
A directory layout is shown, and you choose the fully qualified import that reaches a module inside nested packages.
Consider import math as m followed by print(math.pi). The alias statement binds only the name m in the current namespace, so the name math is undefined and the second line raises NameError. Candidates who think the alias is added alongside the original name pick 3.14159 as the output. Tracing which names each import binds avoids the trap.
Objectives 5
Official objectives from the OpenEDG Python Institute outline, in outline order.
1.1 Import and use modules and packagesImport variants, qualified names for nested modules, dir() and sys.path.
- What it means
- This objective covers the four import forms, reaching names inside nested modules with dotted qualification, listing a module's names with dir(), and the sys.path list Python searches when it looks for a module to import.
- What to understand
- import math binds the name math, so you write math.sqrt(). from math import sqrt binds only sqrt. import math as m binds only m. from math import * copies every public name and can silently overwrite your own names. sys.path is an ordinary list: the script's directory comes first, and appending a path lets Python find modules elsewhere. dir(module) returns a sorted list of the names a module defines.
- How it shows up
- Expect snippets that use a name after a particular import form and ask whether the call succeeds or raises NameError.
- Common confusion
- Assuming import math as m still allows math.pi; the alias replaces the original name in that namespace.
1.2 Perform evaluations using the math moduleceil(), floor(), trunc(), factorial(), hypot() and sqrt().
- What it means
- This objective covers six math functions and the values and types they return. You should be able to evaluate each one by hand for positive and negative arguments.
- What to understand
- ceil() rounds toward positive infinity and floor() toward negative infinity, while trunc() simply drops the fractional part, rounding toward zero. All three return int. sqrt() always returns a float, so sqrt(16) is 4.0. hypot(3, 4) gives the Euclidean distance 5.0. factorial(0) is 1, and factorial() of a negative number raises ValueError.
- How it shows up
- Most items mix negative arguments into ceil(), floor() and trunc() and ask for the printed tuple of results.
- Common confusion
- Treating trunc() and floor() as identical; they differ for negative values, such as -2 versus -3 for -2.7.
1.3 Generate random values using the random modulerandom(), seed(), choice() and sample().
- What it means
- This objective covers producing pseudo-random values with four functions and understanding that a seed makes the sequence repeatable.
- What to understand
- random() returns a float in the half-open range from 0.0 up to but not including 1.0. seed() initializes the generator, so calling it with the same value twice reproduces the same sequence. choice() returns one element of a non-empty sequence. sample(population, k) returns a new list of k unique elements chosen without replacement and raises ValueError when k exceeds the population size.
- How it shows up
- Items ask which outputs are possible, or whether two runs seeded identically print the same numbers.
- Common confusion
- Expecting sample() to repeat elements or accept a k larger than the population; it samples without replacement.
1.4 Discover host platform properties using the platform moduleplatform(), machine(), processor(), system(), version(), python_implementation(), python_version_tuple().
- What it means
- This objective covers querying the operating system, hardware and interpreter that run your code, using functions from the platform module.
- What to understand
- system() returns a generic OS name such as Linux, Windows or Darwin. machine() names the hardware type, processor() the processor (sometimes an empty string), version() the OS version string and platform() a combined descriptive string. python_implementation() returns a name like CPython or PyPy. python_version_tuple() returns three strings, major, minor and patch, not integers.
- How it shows up
- Expect a question asking which function returns a given kind of value or what type an element of the version tuple has.
- Common confusion
- Assuming python_version_tuple() holds integers; every element is a string, so comparisons need conversion.
1.5 Create and use user-defined modules and packagesRationale, __pycache__, __name__, private names, __init__.py and nested packages.
- What it means
- This objective covers writing your own modules and grouping them into packages, including what happens on first import and how Python distinguishes running a file directly from importing it.
- What to understand
- A module runs its top-level code once, on first import, and Python caches compiled bytecode as .pyc files in a __pycache__ directory. When a file is run directly, __name__ equals "__main__"; when imported, it holds the module name. Names starting with an underscore are private by convention and are skipped by from module import *. A package is a directory of modules, traditionally marked with __init__.py, which runs when the package is imported.
- How it shows up
- Items show a package tree and a file using if __name__ == "__main__": and ask what runs when the module is imported.
- Common confusion
- Thinking a leading underscore blocks all access; it only signals intent and excludes the name from star imports.
High-yield concepts
The terms, rules and formulas this domain turns into questions most often.
-
Import binding
Each import form binds a different name in the current namespace.
- Remember
- import x as y binds only y.
- Tested as
- NameError prediction items.
- Trap
- Using the original module name after aliasing it.
-
from module import *
Copies all public names of a module into the current namespace.
- Remember
- It can overwrite existing names silently.
- Tested as
- Name collision snippets.
- Trap
- Expecting underscore-prefixed names to be imported.
-
sys.path
The list of directories Python searches for modules.
- Remember
- It is a list you can modify at runtime.
- Tested as
- Questions on making a module importable.
- Trap
- Thinking sys.path is read-only or a string.
-
dir()
Returns a sorted list of names defined by an object or module.
- Remember
- Called with a module, it lists its attributes.
- Tested as
- Introspection items.
- Trap
- Confusing it with help(), which prints documentation.
-
floor() vs. trunc()
floor() rounds down; trunc() rounds toward zero.
- Remember
- They differ only for negative numbers.
- Tested as
- Output prediction with negative floats.
- Trap
- Assuming the two always match.
-
sqrt() and hypot() return float
Both functions return floating-point results.
- Remember
- sqrt(9) prints 3.0, not 3.
- Tested as
- Type and output questions.
- Trap
- Writing 3 instead of 3.0.
-
seed()
Initializes the pseudo-random generator.
- Remember
- Same seed, same sequence.
- Tested as
- Reproducibility questions.
- Trap
- Believing seed() makes values truly random.
-
sample() vs. choice()
sample() returns k unique items in a list; choice() returns one item.
- Remember
- sample() never repeats an element position.
- Tested as
- Possible-output items.
- Trap
- Expecting choice() to return a list.
-
python_version_tuple()
Returns the interpreter version as a tuple of three strings.
- Remember
- Elements are str, not int.
- Tested as
- Type questions on platform.
- Trap
- Doing arithmetic on the elements without conversion.
-
__name__ == "__main__"
True only when the file is executed directly.
- Remember
- Imported modules see their own module name instead.
- Tested as
- Run-versus-import questions.
- Trap
- Expecting guarded code to run on import.
-
__pycache__
Directory where Python stores compiled .pyc bytecode for imported modules.
- Remember
- It speeds up later imports.
- Tested as
- Package behavior questions.
- Trap
- Thinking it holds source code or is required to run.
-
__init__.py
File that marks a directory as a regular package and runs on package import.
- Remember
- Its code executes once when the package is first imported.
- Tested as
- Package tree items.
- Trap
- Forgetting it runs before submodules load.
Side by side
Concepts candidates mix up, compared on the point that decides the answer.
Import forms
| Concept | Meaning | Key difference | Common trap |
|---|---|---|---|
| import math | Binds the module name | Call as math.sqrt() | Calling sqrt() unqualified |
| from math import sqrt | Binds only the chosen name | Call as sqrt(); math is undefined | Using math.sqrt() afterwards |
| import math as m | Binds an alias only | Call as m.sqrt() | Assuming math is still bound |
| from math import * | Binds all public names | Can overwrite local names | Expecting _private names to come too |
Rounding functions in math
| Concept | Meaning | Key difference | Common trap |
|---|---|---|---|
| ceil(-2.3) | Rounds toward positive infinity | Result -2 | Writing -3 |
| floor(-2.3) | Rounds toward negative infinity | Result -3 | Writing -2 |
| trunc(-2.3) | Drops the fractional part | Result -2, rounds toward zero | Treating it as floor() |
Running vs. importing a module
| Concept | Meaning | Key difference | Common trap |
|---|---|---|---|
| Run directly | __name__ is "__main__" | Guarded block executes | Expecting the file name in __name__ |
| Imported | __name__ is the module name | Guarded block is skipped | Expecting the guarded block to run |
| Imported again | Module object is reused from the cache | Top-level code does not rerun | Expecting prints to repeat |
Exam traps
Each trap is an answer that looks right. Learn the clue that should change your mind.
-
Alias keeps the original
- What candidates pick
- Answer that math.pi prints after import math as m.
- Why it looks right
- Aliases look like nicknames that add a name.
- Why it's wrong
- The alias binds only m, so referencing math raises NameError.
Clue to watch for The snippet uses the full module name after an as clause.
-
Ceil of a negative
- What candidates pick
- Answer -3 for math.ceil(-2.5).
- Why it looks right
- Ceil sounds like moving away from zero.
- Why it's wrong
- ceil() rounds toward positive infinity, so -2.5 becomes -2.
Clue to watch for A negative float is passed to ceil().
-
sqrt returns int
- What candidates pick
- Choose 4 for print(math.sqrt(16)).
- Why it looks right
- The square root of a perfect square is whole.
- Why it's wrong
- sqrt() always returns a float, so the output is 4.0.
Clue to watch for The options include both 4 and 4.0.
-
Oversized sample
- What candidates pick
- Choose a five-element list for sample([1, 2, 3], 5).
- Why it looks right
- It seems the function might repeat items.
- Why it's wrong
- sample() selects without replacement and raises ValueError when k exceeds the population.
Clue to watch for k is larger than the sequence length.
-
Star import gets everything
- What candidates pick
- Expect _helper to be available after from mod import *.
- Why it looks right
- The star suggests all names.
- Why it's wrong
- Names that begin with an underscore are skipped by a star import unless listed in __all__.
Clue to watch for The name in question starts with an underscore.
-
Second import reruns code
- What candidates pick
- Expect a module's print to appear twice after importing it twice.
- Why it looks right
- Each statement looks like it executes the file.
- Why it's wrong
- Python caches loaded modules in sys.modules, so a repeated import reuses the module object.
Clue to watch for The same import statement appears more than once.
Answer framework
For a Section 1 snippet, use these steps to predict exactly what PCAP-31-03 expects the code to do.
- List the names each import statement binds, remembering that aliases replace the original name and star imports skip underscore names.
- Check every later reference against that list; a name that was never bound means NameError at that line, even if the module itself was imported successfully.
- For math calls, decide the rounding direction and the return type, noting that sqrt() and hypot() give floats.
- For random or platform calls, identify the return type and constraints, such as sample() uniqueness and string tuple elements.
- For package or module files, decide whether the file is run or imported, then follow __name__, __init__.py and cached imports to the output.
Practice question
Want more like this? The PCAP practice test has questions for every domain.
Checklist
0 of 12
Tick each one you can do without notes. Your ticks stay in this browser.
Study strategy
Learn this section at the keyboard. Open an interactive interpreter and try each import form in a fresh session, then call dir() to see which names exist afterwards. Alias a module, try to use its original name, and read the NameError so the rule sticks. Append a directory to sys.path and import a module from it.
Next, build a small table for the math functions with inputs 2.7, -2.7 and 4. Write each result and its type, paying most attention to the negative column. Do the same for the random functions: seed the generator twice with the same value, compare the outputs, and make sample() fail on purpose. Call each platform function on your machine and note which ones return strings, tuples or empty values.
Finally, create a package by hand: a top directory, a nested subpackage, __init__.py files that print a message, and a module with a guarded main block. Import it in different ways, run the module directly, and watch when each print appears and where __pycache__ shows up. The Python Institute's free Python Essentials 2 course on Edube is aligned with PCAP, so pair it with timed practice questions afterwards.
FAQ
How much of PCAP is Section 1, Modules and Packages?
Modules and Packages is 12% of PCAP-31-03, and the official syllabus assigns it 6 of the 40 exam items.
Is PCAP-31-03 still the current exam?
Yes. The Python Institute lists PCAP-31-03 as active and PCAP-31-04 as pending release, scheduled for October 31, 2026. Check which version your voucher and testing date cover.
Do I need to memorize every math module function?
No. The syllabus names six: ceil(), floor(), trunc(), factorial(), hypot() and sqrt(). Know their rounding rules, error cases and return types.
What does python_version_tuple() return?
A tuple of three strings holding the major, minor and patch numbers of the running interpreter, for example ('3', '12', '1').
Is __init__.py required for a package?
Modern Python supports namespace packages without it, but PCAP treats __init__.py as the marker of a regular package and expects you to know that it runs on package import.
Why does a module's print statement appear only once?
Python stores loaded modules in sys.modules, so a later import of the same module reuses the existing object instead of running its top-level code again.
What happens with from module import * and private names?
Names beginning with an underscore are not imported by a star import unless the module lists them in __all__.
Test your Domain 1 knowledge
You have covered import forms, sys.path, the math, random and platform modules, and building packages. Test whether you can predict each snippet's output under exam timing.