Free Python Institute PCAP Practice Questions: Section 1: Modules and Packages
Practice 10 free Python Institute PCAP - Certified Associate Python Programmer (PCAP-31-03) questions on Section 1: Modules and Packages, with answers, explanations, and the IT Mastery next step.
Try the IT Mastery web app for a richer interactive practice experience with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Topic snapshot
| Field | Detail |
|---|---|
| Practice target | Python Institute PCAP |
| Topic area | Section 1: Modules and Packages |
| Blueprint weight | 12% |
| Page purpose | Focused sample questions before returning to mixed practice |
How to use this topic drill
Use this page to isolate Section 1: Modules and Packages for Python Institute PCAP. Work through the 10 questions first, then review the explanations and return to mixed practice in IT Mastery.
| Pass | What to do | What to record |
|---|---|---|
| First attempt | Answer without checking the explanation first. | The fact, rule, calculation, or judgment point that controlled your answer. |
| Review | Read the explanation even when you were correct. | Why the best answer is stronger than the closest distractor. |
| Repair | Repeat only missed or uncertain items after a short break. | The pattern behind misses, not the answer letter. |
| Transfer | Return to mixed practice once the topic feels stable. | Whether the same skill holds up when the topic is no longer obvious. |
Blueprint context: 12% of the practice outline. A focused topic score can overstate readiness if you recognize the pattern too quickly, so use it as repair work before timed mixed sets.
Sample questions
These are original IT Mastery practice questions aligned to this topic area. They are not official Python Institute questions, copied live-exam content, or exam dumps. Use them to preview question style and explanation depth before continuing with topic drills, mixed sets, and timed mocks in IT Mastery.
Question 1
Topic: Section 1: Modules and Packages
A developer organizes a small package like this:
# geometry/circle.py
class Circle:
def __str__(self):
return "circle"
# geometry/__init__.py
from .circle import Circle
# main.py
from geometry import Circle
print(Circle.__module__)
What is the role of geometry/__init__.py in this example?
Options:
A. It changes
Circle.__module__togeometry.B. It holds shared class variables for every package class.
C. It exposes
Circlein the package namespace during import.D. It calls
Circle()automatically whengeometryis imported.
Best answer: C
Explanation: When a package is imported, Python executes its __init__.py file. In this example, that file imports Circle from the submodule into the package namespace, so from geometry import Circle works directly.
__init__.py is package-level initialization code. When Python imports geometry, it executes geometry/__init__.py, and any names imported or assigned there become available as attributes of the package. Here, from .circle import Circle makes Circle accessible as geometry.Circle, which is why from geometry import Circle succeeds.
This file does not instantiate the class, manage class variables, or rewrite introspection metadata. The class was defined in geometry.circle, so Circle.__module__ still reflects that defining module. Methods such as __str__() belong to the class definition itself and are unrelated to the role of __init__.py.
The key idea is that __init__.py can initialize a package and control what the package exposes.
- The introspection claim fails because re-exporting a class does not change the module where the class was defined.
- The automatic-constructor claim fails because importing a class name does not create an instance.
- The shared-class-variable claim fails because
__init__.pyis a package module, not a special container for class attributes.
Question 2
Topic: Section 1: Modules and Packages
A developer imports a simple user-defined module named toolbox and checks it in the REPL:
import toolbox
print(dir(toolbox))
['__doc__', '__file__', '__name__', 'build', 'version']
Which TWO statements are justified by this result?
Options:
A.
buildmust be a zero-argument function.B.
versionmust be a string constant.C.
toolbox.buildis a currently available module attribute.D. The list is complete documentation of
toolboxbehavior.E. The output does not show how
buildshould be called.F. The list shows which
sys.pathentry suppliedtoolbox.
Correct answers: C and E
Explanation: From this result, you can safely infer namespace information: build and version are names exposed by toolbox right now. You cannot infer parameter lists, object types, return values, or which sys.path location supplied the module from dir() alone.
dir() is mainly a namespace inspection tool. For a normal module, it shows the names currently available on that module object, so seeing build means the module exposes an attribute with that name. But the list does not describe semantics: it does not tell you whether build is a function, class, or other object, and it does not reveal parameters, return values, side effects, or possible exceptions.
It also does not show how Python found the module during import. Information about import search locations belongs to sys.path, and the actual loaded file path would require checking something like toolbox.__file__, not just reading the name list from dir().
Treat dir() as a discovery aid for names, not as complete API or behavior documentation.
- No signature info The claim that
buildis a zero-argument function fails becausedir()does not show callable signatures. - No type info The claim that
versionmust be a string fails because the list shows only names, not object types. - No search-path details The option about the
sys.pathentry fails becausedir()does not report where the module was found. - Not full documentation The idea that the list completely describes module behavior fails because runtime semantics are not included.
Question 3
Topic: Section 1: Modules and Packages
A developer imports a user-defined module for the first time:
import tools
After the import, the project directory looks like this:
project/
tools.py
__pycache__/
tools.cpython-311.pyc
What is the purpose of the __pycache__ directory?
Options:
A. It records the directories Python searched in
sys.path.B. It saves the names returned by calling
dir()on the module.C. It stores compiled bytecode files for faster future imports.
D. It keeps backup copies of the original module source files.
Best answer: C
Explanation: The __pycache__ directory is used to store cached bytecode files such as .pyc files created when a module is imported. Python can reuse that bytecode later, which can speed up subsequent imports when the source has not changed.
When Python imports a module, it may compile the module’s source code into bytecode and save that bytecode in a .pyc file inside __pycache__. The cache file name often includes information about the Python implementation or version, such as tools.cpython-311.pyc.
This directory is not for source backups, search-path tracking, or namespace inspection results. Its job is simply to cache compiled module bytecode so Python can avoid recompiling unchanged source on later imports. The closest distractor is the idea of a backup copy, but __pycache__ is about execution efficiency, not source preservation.
- Source backup is wrong because
__pycache__stores compiled bytecode, not copies oftools.py. sys.pathrecord is wrong because Python’s module search path is maintained separately and not written there.dir()results is wrong becausedir()inspects names at runtime and does not create cached listings in__pycache__.
Question 4
Topic: Section 1: Modules and Packages
A developer runs this standalone script and gets NameError: name 'random' is not defined.
import random as rnd
nums = [rnd.randint(1, 3) for _ in range(4)]
print(random.choice(nums))
Which change correctly fixes the error while keeping the same behavior?
Options:
A. Replace
import random as rndwithfrom random import choice.B. Replace
random.choice(nums)withrnd.choice(nums).C. Replace
rnd.randint(1, 3)withrandom.randint(1, 3).D. Replace
print(random.choice(nums))withprint(choice(nums)).
Best answer: B
Explanation: import random as rnd creates the local name rnd, not random. Since the script later uses random.choice(nums), it refers to a name that was never imported, so the call must use the alias.
The key concept is namespace binding with import ... as .... In a standalone script, import random as rnd adds only rnd to the current namespace and binds it to the random module. It does not also create a second local name called random.
That is why rnd.randint(...) works but random.choice(...) raises NameError. The correct fix is to use the same alias consistently:
import random as rnd
nums = [rnd.randint(1, 3) for _ in range(4)]
print(rnd.choice(nums))
A common mistake is assuming the original module name remains available automatically after aliasing, but it does not.
- Replacing the import with
from random import choiceremoves the aliasrnd, sornd.randint(...)would fail. - Calling
choice(nums)directly would still fail becausechoicewas not imported into the local namespace. - Changing
rnd.randint(...)torandom.randint(...)repeats the same mistake by using an undefined local name.
Question 5
Topic: Section 1: Modules and Packages
A developer keeps the existing error handling but needs this function to return the smallest integer greater than or equal to delta for any numeric input, using only the already imported math module.
import math
def normalize(delta):
try:
return ???
except ValueError as err:
print(err)
return None
finally:
print("checked")
Which expression should replace ????
Options:
A.
math.ceil(delta)B.
math.floor(delta)C.
math.trunc(delta)D.
math.fabs(delta)
Best answer: A
Explanation: The required operation is the mathematical ceiling: the least integer that is not smaller than the input. With only import math available, math.ceil(delta) is the correct choice and fits the existing try/except/finally structure unchanged.
The core concept is matching the wording of the calculation to the correct math function. math.ceil(x) returns the smallest integer greater than or equal to x, so it is the proper choice whenever the requirement says to round a value up.
math.floor(x) does the opposite by returning the greatest integer less than or equal to x. math.trunc(x) removes the fractional part toward zero, which is different from a ceiling for many negative numbers. math.fabs(x) returns the absolute value as a float, not an integer rounded upward.
The try/except/finally block is just surrounding context here; it does not change which math function performs the requested calculation.
- The option using
math.floor()rounds down, not up. - The option using
math.trunc()drops the fractional part toward zero, so it is not a true ceiling. - The option using
math.fabs()returns absolute value and does not perform integer rounding.
Question 6
Topic: Section 1: Modules and Packages
A developer runs /project/app/main.py directly. The file /project/shared/tools.py exists, and neither /project nor /project/shared is already on sys.path. This code fails with ModuleNotFoundError:
import sys
import tools
print(tools.NAME)
Which change is the best fix?
Options:
A. Insert
sys.append("/project/shared")beforeimport tools.B. Replace
import toolswithfrom sys import tools.C. Replace
import toolswithfrom shared import tools.D. Insert
sys.path.append("/project/shared")beforeimport tools.
Best answer: D
Explanation: Python searches for modules in the directories listed in sys.path. Because tools.py is stored in /project/shared, that directory must be added to sys.path before import tools runs. sys.path is a list, so append() is the appropriate operation.
sys.path is the module search path: a list of directory strings that Python checks when it executes an import. In this scenario, tools.py is in /project/shared, but that directory is not one of the places Python is currently searching, so import tools raises ModuleNotFoundError.
- Access the
pathattribute of thesysmodule. - Use the list method
append()to add/project/shared. - Do it before
import tools.
After that, Python can search /project/shared and load tools.py as the module tools. Import statements use module names, not file paths, and append() belongs to sys.path, not to the sys module.
- Importing
toolsfromsysfails becausetoolsis not a name defined in thesysmodule. - Calling
append()onsysfails because the searchable list issys.path, notsysitself. - Importing with
from shared import toolswould require/projectto be onsys.path, which the stem says is not true.
Question 7
Topic: Section 1: Modules and Packages
A developer needs to choose 3 different backup servers from a list, with no repeats. The current code is broken:
import random
servers = ["s1", "s2", "s3", "s4", "s5"]
picked = random.choice(servers, 3)
print(picked)
Which replacement for the picked = ... line is the best fix?
Options:
A. picked = sample(servers, 3)
B. picked = random.choice(servers)
C. picked = random.choices(servers, k=3)
D. picked = random.sample(servers, 3)
Best answer: D
Explanation: random.sample(population, k) is the correct API when you need k unique elements chosen without replacement. Because the code uses import random, the function must be called as random.sample(...).
The core concept is choosing unique items without replacement from the random module. random.choice() returns a single element, so it cannot directly produce three distinct servers. In this case, the correct fix is to replace the broken call with random.sample(servers, 3), which returns a list of three different elements from the given population.
import randommeans you access functions with the module prefix, such asrandom.sample(...).sample(population, k)selectskunique items.- This matches the requirement of “3 different backup servers.”
The closest distractor is the call to random.choices(...), but that function samples with replacement, so duplicates can appear.
random.choicesmismatch can return the same server more than once, so it does not guarantee unique results.- Missing module prefix fails because
sample(...)is not defined in the local namespace after onlyimport random. - Single-item API returns just one server, not three selections.
Question 8
Topic: Section 1: Modules and Packages
A developer has this project layout:
project/
main.py
bakery/
prices.py # TAX = 0.23
main.py contains:
from bakery import TAX
print(TAX)
Running main.py raises an import error. Many existing modules already use from bakery import TAX, so the team wants a package-level fix without changing those imports. What is the best correction?
Options:
A. Add
bakery/__init__.pywithfrom .prices import TAXB. Change
main.pytoimport bakery.prices as TAXC. Change
main.pytofrom prices import TAXD. Add
bakery/__init__.pywithimport prices
Best answer: A
Explanation: from bakery import TAX expects TAX to be available in the bakery package namespace. Adding bakery/__init__.py and importing TAX there exposes the name at package level and preserves all existing imports.
__init__.py is the package initialization file. In this scenario, Python imports the bakery package first, then looks for TAX inside that package namespace because the code uses from bakery import TAX. Adding bakery/__init__.py with from .prices import TAX makes the constant available directly from the package.
from .prices import TAXis a relative import from a module inside the same package.- This keeps the public package interface stable for all existing callers.
- It also makes the package export explicit instead of forcing every file to know the internal module name.
Changing the caller to import prices.py directly would bypass the package-level design the stem asks you to preserve.
- Using
from prices import TAXtreatspricesas a top-level module instead of a module insidebakery. - Using
import bakery.prices as TAXbindsTAXto the module object, not to the constant value. - Using
import pricesinside__init__.pyis the wrong import target for a sibling module in this package layout. - Re-exporting
TAXfrom__init__.pypreservesfrom bakery import TAXacross existing files.
Question 9
Topic: Section 1: Modules and Packages
During a deterministic test run, tests/test_picker.py imports picker and calls sample_ids() twice. The team wants every fresh program start to produce the same two lists, but the second call in the same run must continue the pseudo-random sequence rather than restart it.
# picker.py
import random
def sample_ids():
return [random.randint(100, 999) for _ in range(3)]
Which change best meets this requirement?
Options:
A. Add
random.seed(7)as the first line insidesample_ids().B. Add
random.seed()once afterimport random.C. Add
random.seed(7)once afterimport random.D. Add
seed(7)once afterimport random.
Best answer: C
Explanation: random uses a pseudo-random generator whose output depends on its seed. Calling random.seed(7) once before generating values gives the same starting state on each fresh run, while later calls in that run continue from the current state.
The core concept is that Python’s random module is deterministic after you set a fixed seed. A seed initializes the generator’s internal state, so using the same seed at the start of each fresh program run produces the same sequence of values every time.
Because the module was imported with import random, the function must be called with its qualified name: random.seed(...).
- Seed once before any random values are generated.
- Use a fixed value such as
7for reproducible tests. - Do not reseed inside the function unless you want each call to restart the sequence.
The closest trap is reseeding inside sample_ids(), which repeats the beginning of the sequence on every call instead of continuing it.
seed(7)fails because only the module name was imported, soseedis not available directly.- Seeding inside
sample_ids()restarts the generator for each call, so the sequence does not continue within one run. random.seed()without an argument does not create a fixed, repeatable test sequence across fresh runs.
Question 10
Topic: Section 1: Modules and Packages
Two files are in the same directory. What is printed when main.py is run?
# tools.py
label = "PCAP"
def show():
return label.lower()
# main.py
import tools
from tools import label
print(label)
print(tools.label)
try:
print(show())
except NameError:
print("no-show")
print(tools.show())
Options:
A. The program prints:
PCAP,PCAP,pcap,pcap.B. The program prints three lines, then raises
AttributeErrorattools.show().C. The program prints:
PCAP,PCAP,no-show,pcap.D. The program raises
NameErrorbefore printing anything.
Best answer: C
Explanation: import tools makes only the module name tools available in main.py. from tools import label adds label directly to the current namespace, but it does not add show, so show() fails while tools.show() succeeds.
The key idea is that the two import forms create different names in the importing module.
import tools binds one local name: tools. To reach objects from that module, you use qualified names like tools.label and tools.show().
from tools import label binds a second local name: label. That is why print(label) works directly. But show was never imported into main.py as a standalone name, so show() raises NameError. The except NameError block catches that and prints no-show. Execution then continues, and tools.show() returns the lowercase form of "PCAP", which is pcap.
So the output is two PCAP lines, then no-show, then pcap.
- Direct function access fails because
import toolsdoes not makeshowa standalone local name. - Missing
labelis incorrect becausefrom tools import labelplaceslabeldirectly inmain.py. - Missing module attribute is incorrect because
showis defined insidetools, sotools.show()is valid.
Continue in the web app
Use IT Mastery for interactive Python Institute PCAP practice with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Try Python Institute PCAP on Web