PCAP-31-03 — Python Associate Scenario Practice Guide
Learn how to read PCAP-31-03 Python scenarios, trace code, interpret constraints, and choose the most defensible answer.
How to approach PCAP-31-03 scenario questions
The Python Institute PCAP - Certified Associate Python Programmer exam, PCAP-31-03, tests whether you can reason through Python behavior, not just remember syntax. Many questions present a short code sample, a stated requirement, or a partial implementation and ask you to choose the result, the exception, the best correction, or the most appropriate programming construct.
A strong scenario-reading habit helps you avoid guessing from memory. Your goal is to slow the question down, identify the actual decision point, and choose the answer that is most defensible from the facts given.
This guide is independent exam-preparation guidance and is not affiliated with Python Institute.
Start with the decision point
Before tracing every line, find what the question is really asking.
Common PCAP scenario decisions include:
- What will be printed?
- What value will a variable contain?
- Which exception will be raised, if any?
- Which statement correctly describes the code?
- Which implementation satisfies the requirement?
- Which class, method, or attribute behavior is correct?
- Which module, package, import, or namespace rule applies?
- Which change fixes the code while preserving the intended behavior?
Read the final sentence of the question carefully. A scenario that contains code may not always ask for output. It may ask for the first error, the best replacement line, or the explanation of an object-oriented behavior.
Use this quick framing question:
“Am I predicting execution, selecting a correction, or matching a concept to a requirement?”
That answer determines how you should read the rest of the scenario.
Classify the scenario before solving it
PCAP scenarios often fall into several broad Python reasoning categories. Classifying the scenario early helps you choose the right mental model.
Code execution scenarios
These ask what happens when the code runs.
Focus on:
- execution order
- assignments and rebinding
- mutable versus immutable objects
- function calls
- loop behavior
- exception flow
- printed output versus returned values
Exception scenarios
These ask whether code runs successfully or which exception appears.
Focus on:
- invalid syntax versus runtime exception
- the first failing line
- exception hierarchy
- matching
exceptclauses elseandfinallyexecution- whether code after the exception is reachable
Object-oriented scenarios
These ask about classes, objects, inheritance, attributes, methods, or encapsulation-like conventions.
Focus on:
- class definition versus object creation
- constructor execution
- instance attributes versus class attributes
- method binding through
self - inheritance and overriding
- simple method resolution behavior
- name mangling for double-underscore attributes, when relevant
Data and collection scenarios
These ask about lists, tuples, dictionaries, sets, strings, slicing, comprehensions, or iteration.
Focus on:
- object mutability
- indexing and slicing boundaries
- whether an operation mutates or returns a new object
- dictionary key behavior
- membership tests
- iteration order where applicable
- shallow copies and aliases
Module, package, and file scenarios
These ask about imports, namespaces, packages, standard library usage, or basic file handling.
Focus on:
- what name is imported
- where a name is looked up
- whether a module is executed on import
- file mode and file pointer behavior
- context managers
- text processing expectations
Read the code like Python executes it
For output and behavior questions, do not read the code as a human summary. Read it as the interpreter would execute it.
A practical sequence:
Check whether the code can be parsed. Syntax errors occur before normal execution. If the code is syntactically invalid, runtime tracing is not the main issue.
Identify definitions. Function and class bodies are defined first, but most internal statements do not execute until the function is called or the object is created.
Track top-level execution. Move line by line through assignments, calls, loops, and conditionals.
Enter functions only when called. Create a small local-scope note for parameters and local variables.
Return to the caller. Replace the function call with its returned value, or note that it returns
Noneif there is no explicit return.Stop at the first unhandled exception. If an exception is not caught, later ordinary statements do not run.
Record only observable results. If the question asks for printed output, track
print()calls. If it asks for a final value, track variable state.
Build a compact trace, not a full transcript
You do not need to rewrite the whole program. You need a scratchpad that captures changing state.
For example:
values = [1, 2, 3]
alias = values
values.append(4)
print(alias)
A useful trace is:
valuesandaliasrefer to the same listappend(4)mutates that listprint(alias)displays the mutated list
So the output is based on [1, 2, 3, 4].
Now compare:
values = [1, 2, 3]
alias = values
values = values + [4]
print(alias)
A useful trace is:
aliasrefers to the original listvalues + [4]creates a new listvaluesis rebound to the new listaliasstill refers to the original list
So alias is still based on [1, 2, 3].
The habit is more important than the specific example: ask whether the code mutates an existing object or rebinds a name to a different object.
Separate facts from assumptions
Scenario questions often include facts that matter and facts that merely set context. Your job is to decide which facts affect Python behavior.
Facts that usually matter
Pay close attention to:
- initial variable values
- data types
- indentation
- function call arguments
- default parameter values
- class and instance attribute assignments
- import style
- exception type and handler order
- file mode
- loop conditions
break,continue, andreturn- whether a method mutates an object or returns a new one
Facts that may not matter
Do not over-weight details that do not change execution, such as:
- variable names that sound meaningful but behave normally
- comments that do not match the code
- familiar-looking patterns that are slightly altered
- unused imports or unused variables
- answer choices that describe good style but not the actual behavior
If the code says one thing and the narrative implies another, the code usually controls the execution result. If the question asks for the best implementation, then the requirement controls the answer.
Identify the environment and available names
In Python scenarios, the “environment” means the names, modules, and objects available at the point of execution.
Ask:
- Has the name been assigned before it is used?
- Was the function or class defined before it is called?
- Was the module imported?
- Was the name imported directly, or is it accessed through the module namespace?
- Is the name local, global, built in, or an attribute of an object?
- Does an inner assignment make a name local within a function?
For example:
import math
print(math.sqrt(16))
The name math is available, and sqrt is accessed through the module.
But:
from math import sqrt
print(math.sqrt(16))
Here sqrt is imported directly. The name math is not automatically available from that import statement. The scenario decision turns on namespace, not on whether sqrt exists in the standard library.
Use scope rules deliberately
Many PCAP questions become simpler when you ask where Python will look for a name.
Use the LEGB idea as a practical checklist:
- Local: names assigned inside the current function
- Enclosing: names in enclosing functions, when nested functions are involved
- Global: names assigned at module level
- Built-in: built-in names such as
len,str, orException
When a function assigns to a name, Python generally treats that name as local within the function unless global or nonlocal changes the rule.
Read carefully before assuming that a global value is being changed.
x = 10
def change():
x = 20
change()
print(x)
The assignment inside change() creates or updates local x, not the module-level x. The printed value is based on the global x, which remains 10.
The key scenario-reading habit:
A name that looks the same is not always the same variable in the same scope.
Distinguish print, return, and expression value
A common decision point is whether a function displays a value, returns a value, or both.
Read these separately:
print(x)sends text to standard output and returnsNone.return xsends a value back to the caller.- An expression by itself may compute a value without displaying or returning it in a useful way.
- A function without an explicit
returnreturnsNone.
For scenario questions, ask:
- Does the question ask what is printed?
- Does it ask what is returned?
- Does it ask what a variable receives from a function call?
- Is the result of
print()being assigned?
Example:
def show(x):
print(x)
result = show(5)
print(result)
Trace:
show(5)prints5show()has no explicit return, so it returnsNoneresultbecomesNone- the final
print(result)printsNone
The observable output has two lines: one for 5 and one for None.
Trace control flow before evaluating answer choices
Answer choices can pull your attention toward plausible outcomes. Trace first, then compare.
Conditionals
For if, elif, and else blocks:
- evaluate conditions in order
- execute only the first matching branch
- skip remaining branches after a match
- remember truthiness rules for empty collections, zero,
None, and non-empty objects
Loops
For for and while loops:
- identify the iteration source or loop condition
- track variables changed inside the loop
- watch
breakandcontinue - know that loop
elseruns only if the loop completes withoutbreak
Example:
for n in [1, 2, 3]:
if n == 2:
break
else:
print("done")
print("after")
Because the loop exits with break, the loop else block does not run. The final print("after") still runs.
Exceptions
For try, except, else, and finally:
tryruns first- matching
excepthandles an exception elseruns only if no exception occurs in thetryblockfinallyruns whether or not an exception occurs, subject to normal program termination behavior
When multiple except clauses appear, order matters. A more general exception handler placed before a more specific one may handle the exception first.
Read data structure operations by effect
Many scenario answers depend on whether an operation changes an object, creates a new object, or returns a value.
Lists
Ask:
- Is the list mutated?
- Is a new list created?
- Is a method returning
Noneafter mutating? - Are two names aliases for the same list?
Typical reasoning points:
append,extend,insert,remove,sort, andreversemutate the list.- Slicing can create a new list.
- Assignment copies a reference, not the object itself.
- A shallow copy duplicates the outer list, not necessarily nested objects.
Tuples
Ask:
- Is the tuple itself being modified?
- Does it contain mutable elements?
- Is a comma creating the tuple, not just parentheses?
A tuple is immutable as a container, but it can hold references to mutable objects.
Dictionaries
Ask:
- Which keys exist?
- Are keys compared by equality and hash behavior?
- Is the code accessing a missing key directly?
- Does the method return a default or change the dictionary?
Focus on the actual method used, such as direct indexing, get, update, keys, values, or items.
Strings
Ask:
- Is the operation returning a new string?
- Are indexes or slices within range?
- Is the comparison case-sensitive?
- Is whitespace included?
- Is the method being called on the correct object?
Strings are immutable. Methods such as replace, strip, lower, and upper return new strings; they do not change the original string in place.
Interpret object-oriented scenarios as state changes
For class-based questions, first separate class definition from object behavior.
Use this sequence:
List the classes. Note inheritance relationships.
Find constructors. Identify
__init__methods and whether a parent constructor is called.Track object creation. Each instance gets its own instance attributes assigned through
self.Separate class attributes from instance attributes. A class attribute is shared through the class until an instance shadows it with its own attribute.
Resolve method calls. Start with the instance’s class, then look through the inheritance chain.
Track modifications through
self. Assignments such asself.x = ...affect that object’s instance state.
Example:
class Counter:
total = 0
def __init__(self):
Counter.total += 1
a = Counter()
b = Counter()
print(Counter.total)
The decision point is not whether a and b are different objects. They are. The important fact is that total is a class attribute updated through the class name. After two object creations, the printed value is based on 2.
For a different pattern:
class Counter:
total = 0
def __init__(self):
self.total = 1
a = Counter()
b = Counter()
print(Counter.total)
Here self.total = 1 creates an instance attribute for each object. The class attribute Counter.total remains 0.
A small difference in where the assignment occurs changes the answer.
Match implementation choices to requirements
Some PCAP questions ask for the best code fragment to satisfy a stated requirement. Treat these like miniature design decisions.
First identify the requirement precisely:
- Must the original collection remain unchanged?
- Must duplicates be preserved?
- Must order be preserved?
- Must the code handle missing values?
- Must the result be numeric, string, Boolean, list, tuple, dictionary, or set?
- Must the code read from a file or process an already available string?
- Must the code be reusable as a function?
- Must the solution use a class or inheritance concept?
Then eliminate choices that:
- do not run
- produce the wrong type
- change input data when the requirement implies preservation
- ignore a stated condition
- depend on a name that was not defined or imported
- handle only part of the scenario
The best answer is not always the shortest or most familiar. It is the one that satisfies the stated requirement under the facts provided.
Separate constraint from preference
Scenario wording often includes both hard constraints and softer preferences.
A hard constraint changes the correct answer:
- “must not modify the original list”
- “must handle missing keys”
- “must preserve the order”
- “must close the file after reading”
- “must use inheritance”
- “must catch only the relevant exception”
- “must work when the input string contains leading spaces”
A preference may suggest style but may not determine correctness unless the question asks for best practice:
- “the developer wants a concise solution”
- “the code should be easy to read”
- “the function will usually receive small inputs”
- “the variable is named
result”
When a hard constraint conflicts with a convenient answer, the constraint wins.
Choose the least disruptive correction
When the scenario asks how to fix code, look for the answer that corrects the actual failure while preserving the intended behavior.
A good correction usually:
- changes the smallest relevant part of the code
- keeps the same input and output meaning
- respects existing variable names and data structures where possible
- fixes the actual failing line or concept
- does not introduce a new dependency unless required
- avoids catching or hiding errors that the requirement does not mention
For example, if a file scenario fails because the code attempts to read from a closed file, the best answer is likely about file lifetime or indentation with a context manager, not about changing the data-processing algorithm.
If a class scenario fails because an instance method lacks self, the correction should address the method definition or call pattern, not replace the class with unrelated procedural code.
Use exception logic as a decision tree
For exception scenarios, avoid scanning for a familiar exception name and choosing quickly. Walk the flow.
Ask:
- Does the code parse?
- What is the first line that can fail at runtime?
- What exception type would that line raise?
- Is that exception caught by an
exceptclause? - If caught, what code runs next?
- If not caught, what output occurred before the exception?
- Does
finallyrun? - Does execution continue after the
trystatement?
Example:
try:
print(10 / 0)
except ValueError:
print("value")
finally:
print("done")
Trace:
10 / 0raises a division-related exceptionValueErrordoes not matchfinallystill runs and printsdone- the exception remains unhandled after
finally
So an answer that says only "done" is incomplete if the question asks what happens when the program runs. The program prints from finally, then terminates with an unhandled exception.
Read file scenarios operationally
For basic file-handling scenarios, identify the file state and the operation order.
Ask:
- Is the file opened for reading, writing, appending, or another mode?
- Is the file object still open at the point of use?
- Is the code inside or outside the
withblock? - Has the file pointer already moved because of a previous read?
- Is the code reading the whole file, one line, or a list of lines?
- Is the result a string or a list?
- Are newline characters part of the returned data?
Prefer reasoning from the stated file content and the exact method used. Do not assume extra lines, hidden data, or automatic pointer resets unless the code performs them.
Read import and package scenarios by namespace
Import questions often turn on what name exists after the import statement.
Compare the patterns:
import module
Use names as module.name.
from module import name
Use name directly.
import module as alias
Use alias.name.
from module import name as alias
Use alias directly.
When a scenario asks which expression works, decide which namespace contains the required name. Do not choose an answer just because it resembles a common import pattern.
Also remember that module code can execute when imported. If a scenario includes top-level statements in a module, consider whether importing that module causes those statements to run.
Pay attention to syntax before semantics
Some answer choices are conceptually reasonable but syntactically invalid. For PCAP-level Python scenarios, syntax details can be decisive.
Check:
- indentation
- colons after compound statements
- parentheses, brackets, and braces
- string quotes
- correct use of
=,==, and other operators - valid function definitions
- valid class definitions
- valid
tryandexceptstructure - whether
returnappears inside a function - whether
breakorcontinueappears inside a loop
If a code fragment cannot be parsed, it cannot be the correct runtime behavior.
Use answer choices as evidence, but not as the starting point
After your own trace, compare your result to the choices. If your exact result is present, still verify formatting and question wording.
Check:
- line breaks in printed output
- spaces and quotes
- list, tuple, dictionary, and set display forms
- whether strings are shown with or without quotes
- whether the answer says “an exception is raised” instead of giving output
- whether the question asks for the first output, final output, final value, or complete behavior
If none of the choices matches your trace, revisit the decision point. You may have answered a different question than the one asked.
A practical PCAP scenario-reading checklist
Use this compact checklist during final review:
- What is the question asking me to decide?
- Is the code syntactically valid?
- Which lines are definitions, and which lines execute now?
- What names exist at this point?
- Which objects are mutable?
- Are any names aliases for the same object?
- What is local, global, class-level, or instance-level?
- Does the operation mutate, return a new object, or return
None? - What is the first possible exception?
- Is the exception handled?
- What exactly is printed, returned, or assigned?
- Which answer satisfies the stated requirement with the fewest unsupported assumptions?
Final review practice method
For efficient PCAP-31-03 preparation, practice scenario questions in short, deliberate sets:
Classify each question first. Output, exception, OOP, collection, import, file, or implementation choice.
Trace before looking deeply at the choices. Write two or three state notes instead of trying to hold everything in memory.
Commit to a reason. Before selecting an answer, state the Python rule that supports it.
Review missed questions by concept. Group misses by scope, mutability, exceptions, OOP, strings, files, or imports.
Repeat under time pressure. Once your reasoning is accurate, use timed mixed practice to build exam readiness.
Next step
Use scenario practice to strengthen your weakest Python reasoning areas first, then move into mixed topic drills and full mock exams. For each missed question, record the exact fact you overlooked and the Python rule that would have led to the most defensible answer.