Practise Certified Associate Python Programmer PCAP practice questions — original exam-style scenarios covering every exam domain, with detailed explanations, wrong-answer analysis, and common exam traps.
Why wrong: Output 1 1 would occur only if the instance attribute had never been assigned, so e.attr still resolved to the class attribute value of 1. The line that sets the instance attribute to 2 is present, however, and that assignment does not modify Example.attr; it merely creates a separate entry in the instance's own namespace. Thus choosing 1 1 ignores the shadowing effect of the instance attribute.
B
0 1
Why wrong: The value 0 is simply not present anywhere in this code, so this output cannot be produced by any valid execution path. To get 0 1, the class attribute would have to be initialized to 0 (for example, attr = 0) or some method would have to return 0 before the first print. Since Example.attr is hard-coded as 1, the first printed value is inevitably 1, making 0 1 an impossible result.
C
1 2
This is correct because the two values are read from different namespaces. Example.attr directly accesses the class attribute, which remains 1 after assignment. In contrast, e.attr = 2 creates an instance attribute named 'attr' that shadows the class attribute during attribute lookup on that instance, so the instance dump returns 2. Therefore the output is exactly 1 2.
D
2 2
Why wrong: 2 2 would require the assignment e.attr = 2 to overwrite the class attribute itself, but Python semantics do not do that. Attribute assignment on an instance always writes to the instance's own __dict__, leaving the unchanged class attribute at 1 for future class-level access. Only a direct assignment like Example.attr = 2 would change the class attribute, and that is not what the code does.
Refer to the exhibit. A developer ran the script and saw the above traceback. The intended behavior was to load a JSON configuration file, and if the file is missing, create a default config. What is the most likely root cause of the second exception (NameError)?
Exhibit
Traceback (most recent call last):
File "app.py", line 9, in <module>
with open("config.json", "r") as f:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'config.json'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "app.py", line 11, in <module>
config = json.load(f)
^^^^^^^^^^^
NameError: name 'json' is not defined
A
The variable 'f' was not defined due to the FileNotFoundError.
Why wrong: The FileNotFoundError from open() would have stopped execution before reaching json.load(f), so f would never be the cause of a NameError. In the actual traceback, the NameError explicitly says name 'json' is not defined, meaning the interpreter evaluated json first and failed there; f is defined if open succeeded. Even if the file were missing, the first exception would be FileNotFoundError, not a NameError about json. Thus, the root cause is not f being undefined.
B
The script did not import the json module.
NameError: name 'json' is not defined means Python could not find a binding for the name 'json' in any accessible scope. The json module is part of the standard library but is not automatically loaded; it must be brought into scope with an explicit import json statement. Since the script calls json.load(f) without having imported json, the name lookup fails at runtime. This is the classic missing-import error and is unrelated to the file's existence, content, or open mode.
C
The config.json file exists but is empty.
Why wrong: If config.json existed but were empty, json.load(f) would raise a json.JSONDecodeError because an empty file is not valid JSON. The traceback's first exception is FileNotFoundError, which proves the file is absent; an empty file would still be found by open() and would not produce that error. Additionally, a JSONDecodeError would occur inside the json module only after the name 'json' has already been resolved, so it could never manifest as a NameError.
D
The file was opened in binary mode instead of text mode.
Why wrong: The file-open mode has no effect on module imports, so using binary mode could not cause a NameError for 'json'. If the script had used 'rb', json.load would still require the json name to be defined; a missing import would produce the same NameError regardless of mode. Since the traceback shows FileNotFoundError before any reading happens, mode-specific behavior never even gets a chance to run, and changing the mode would not fix the missing import.
Why wrong: The print() function writes the string's actual contents to standard output, without adding any quotation marks. The quotes in the source code are delimiters that define the string literal, but they are not part of the string value itself. To display quotes, one would need to include them explicitly or use repr(), so showing '100' with quotes is not what print('100') produces.
B
100
Official answer: print('100') displays the sequence of characters 1, 0, 0 on the console. The print() function strips the syntactic quotes and outputs the raw string content, so the visible result is 100 without surrounding quotation marks. This is the standard behavior of print() in Python 3.
C
True
Why wrong: Although '100' is a non-empty string and would be considered truthy in a boolean context like an if condition, print('100') does not evaluate its argument as a boolean. The print() function simply converts the argument to its string representation and writes it out. The output is the literal characters 1, 0, and 0, not the keyword True.
D
Error
Why wrong: The code print('100') is perfectly valid; it uses the built-in print() function with a string literal argument. No syntax or runtime error occurs because the parentheses and quotes are balanced and the string is well-formed. The program will execute and output the characters 100 to the console.
Refer to the exhibit. A developer is writing a script to read this JSON configuration file. The script should write the logging configuration to a separate file called 'logging.conf'. Which file mode should be used to create the file if it doesn't exist, and overwrite it if it does?
Why wrong: Mode 'x' stands for exclusive creation: it attempts to create a new file for writing but immediately raises FileExistsError if the given path already exists. Because the script likely expects to overwrite an existing file (or create it if missing), this mode would abort the operation instead of truncating and rewriting the content, making it incorrect for the intended behavior.
B
'r+'
Why wrong: Mode 'r+' opens the file for both reading and writing without truncating it, and it raises FileNotFoundError if the file does not exist. Since this mode never creates a missing file, it cannot fulfill a requirement to write to a fresh output, and any writes overwrite bytes at the current file position, leaving leftover trailing data from longer prior content rather than producing a clean result.
C
'a'
Why wrong: Mode 'a' opens the file for appending, positioning the file pointer at the end so that all writes go to the end regardless of seek positions, and it never truncates existing content. Using 'a' would cause new data to accumulate after any prior contents, resulting in a file that mixes old and new output, which is not a clean replacement of the file's contents.
D
'w'
Mode 'w' opens the file for writing, creating the file if it does not exist and truncating it to zero length if it does, thereby discarding all previous contents. This exactly matches the need to write a complete, fresh set of data: any existing content is erased before the first write, ensuring that the final file contains only the current output with no stale data.
Refer to the exhibit. Which of the following is the most likely cause of this error?
Exhibit
Traceback (most recent call last):
File "main.py", line 1, in <module>
from mypackage import mymodule
ImportError: cannot import name 'mymodule' from 'mypackage' (unknown location)
A
The __init__.py file in mypackage is empty.
Why wrong: An empty __init__.py is not a problem: it is the standard way to mark a directory as a Python package, and the file may legitimately contain nothing. The existence of __init__.py makes the directory importable, after which `from mypackage import mymodule` resolves mymodule by looking for a submodule or an attribute defined in the package. An empty __init__.py does not suppress submodule discovery, so it cannot be the reason the import fails.
B
There is a circular import between mypackage and mymodule.
Why wrong: A circular import between a package and its own submodule would produce a different error, typically a partial initialization message like 'cannot import name X from partially initialized module' or simply AttributeError at import time. Here the error names mymodule from mypackage, but if mypackage were a real package, mymodule would be a separate module object, and circular imports would not cause mypackage to lose that submodule. The 'cannot import name' error with an unknown location points instead to mypackage being a plain module, not a circular dependency.
C
mymodule.py does not exist in mypackage directory.
Why wrong: If mymodule.py were absent from a genuine package, Python would raise `ModuleNotFoundError: No module named 'mypackage.mymodule'`, not `ImportError: cannot import name`. In a package, a missing submodule is a module-not-found condition because the import system searches the package's filesystem for the module file. The current error message explicitly says 'cannot import name from mypackage', which names mymodule as a missing attribute rather than a missing file, so the absent-file explanation does not fit.
D
mypackage is a module file, not a package directory.
When mypackage is a single-file module, it contains no namespace for submodules, so `from mypackage import mymodule` treats mymodule as an attribute that must exist in that file. Since no such attribute is defined, the import machinery raises `ImportError: cannot import name 'mymodule' from 'mypackage'` with the file location of the module. This is the most likely cause because the traceback location is the mypackage module itself, not a package directory, and it aligns with how Python distinguishes modules from packages.
Refer to the exhibit. What is the output of the code?
Exhibit
with open('file.txt', 'r') as f:
data = f.read()
print(f.closed)
# Output: False
A
Error
Error is the correct outcome because the code attempts to open a non-existent file inside the try block, which raises a FileNotFoundError (a subclass of OSError). The except clause, as written, does not match this exception type—probably catching a different exception or being absent—so the exception is left unhandled. Python's interpreter then prints a traceback describing the error and stops execution, meaning no normal output is produced and the program terminates with an error status.
B
True
Why wrong: True would be the output only if the try block executed successfully and the code reached an explicit print(True) or an expression evaluating to True. But when the file cannot be found, the open() call raises an exception before any such print statement is encountered. Because the except clause fails to catch the raised exception, the program never continues to the line that would output True; instead, the unhandled exception triggers a traceback and the program errors out.
C
False
Why wrong: False would require the program to reach a print(False) or an expression that evaluates to False, typically after a successful operation or after the exception is caught by a matching handler. Here, the missing-file exception is not caught by the except clause, so the normal flow of execution is interrupted and jumps straight to the interpreter's error reporting. Consequently, the user sees a traceback and an error message, not the literal output False.
D
None
Why wrong: None could be displayed if the code executed a print statement with no argument or printed the result of a function that returns None, such as print(None) or print(some_none_returning_func()). In this scenario, the try block raises an unhandled exception—the file does not exist—and the except clause does not handle it, so the execution never reaches any print statement. The interpreter instead outputs an exception traceback, which is an error, not the Python value None.
Refer to the exhibit. Given the project structure, which of the following import statements in main.py would cause an ImportError?
Exhibit
Exhibit:
Project structure:
main.py
utils/
__init__.py
helpers.py
strings/
__init__.py
format.py
# main.py
from utils.helpers import greet
from utils.strings.format import bold
A
from utils import strings
Why wrong: This is a normal absolute import, not the one that triggers an ImportError. When main.py is executed from the project root, the root directory is on sys.path, so utils is a top-level package and from utils import strings correctly binds the utils.strings subpackage to the name strings. Because the statement runs without error, it is a valid program line and therefore cannot be the correct answer to a question about an invalid import.
B
from ..utils import helpers
The leading double dot in from ..utils import helpers marks this as a relative import that climbs one level above the current package. If this line appears in main.py at the project root, main.py is being executed as the __main__ module rather than as an importable package member, so its __package__ is empty and there is no parent package to resolve the dots. This raises ImportError: attempted relative import with no known parent package, which is exactly why this is the only statement that fails and the correct answer.
C
from utils import helpers
Why wrong: Although it resembles the correct option, this line is an absolute import, not a relative one. Since utils is a package directory containing __init__.py, and the project root is on sys.path when main.py is run, Python resolves utils as a top-level package and imports the helpers submodule without any issue. This valid absolute import succeeds, so it is a wrong answer choice for a question about an import that will fail.
D
from utils.strings import format
Why wrong: This statement uses an absolute dotted-path import that descends into the utils package and then into the utils.strings subpackage. As long as utils.strings is an importable subpackage with its own __init__.py (or a module object), from utils.strings import format resolves the format attribute or submodule correctly and binds it as a local name. It executes without raising the relative-import error, so this option is not the requested invalid import and is marked wrong.
Refer to the exhibit. What is the output? (Note: actual MRO may vary; choose the one that matches Python 3 C3 linearization.)
Exhibit
class A:
def method(self):
return "A"
class B(A):
def method(self):
return "B"
class C(A):
def method(self):
return "C"
class D(B, C):
pass
print(D.__mro__)
Why wrong: This tuple omits object, the universal root of Python's new-style class hierarchy. C3 linearization always appends object as the final class, because every class ultimately inherits from it even when no explicit base is written. Removing object would leave the MRO without the required common-superclass fallback for special methods like __repr__ and __eq__, so this cannot be what D.__mro__ actually returns.
This is the exact tuple produced by C3 linearization for class D(B, C), where both B and C inherit from A. The merge step selects B first because it is the declared first base of D and is not a tail of any other candidate list; it then selects C, followed by A, and finally object. This order respects both the local precedence D(B, C) and the monotonicity rule that the MROs of B and C remain prefixes of D's MRO.
Why wrong: C3 linearization must preserve the order in which base classes are written in the class statement: since D is defined as D(B, C), B must precede C in D's MRO. This option reverses that declaration order, which is what you would expect for D(C, B), not for the actual source code. It also wrongly places C's entire branch ahead of B's branch, directly contradicting the explicit base-class order.
Why wrong: This order places A ahead of B and C even though A is only an indirect ancestor of D. In C3, a class must always be listed after its direct bases, and the direct bases of D—B and C—must appear in their declared order before any common ancestor like A is considered. This is a depth-first-style guess that puts the shared ancestor first, violating the local precedence constraint that C3 enforces and breaking the expected super() resolution path.
class Cache:
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args):
if args in self.cache:
return self.cache[args]
result = self.func(*args)
self.cache[args] = result
return result
@Cache
def add(a, b):
return a + b
print(add(1, 2))
print(add(1, 2))
A
3\n6
Why wrong: This output would require the function to produce different values on identical successive calls, such as a counter that increments each invocation. However, the exhibit uses caching: the second call with the same argument bypasses the function body and returns the previously stored result, so no modification occurs and the value remains 3.
B
3\nError
Why wrong: This output would mean the first call succeeded but the second threw an exception, perhaps due to an exhausted iterator or a mutated argument. Caching, however, means the second call never re-executes the function code; it retrieves the cached return value. Since the first call completes without error, the second call cannot introduce a new error from the function body.
C
Error\n3
Why wrong: This output would indicate the first call raised an exception and the second somehow succeeded, which contradicts how caching behaves in the exhibit. In a cached function, the first call executes and must finish normally to store a result; the output shows a successful computation of 3. Therefore, there is no error on the first call, making this sequence impossible.
D
3\n3
On the first invocation, the function computes and returns 3, and the cache stores that result keyed by the argument. The second invocation sees the argument already in the cache and immediately returns the stored value 3 without re-entering the function. This behavior is exactly what caching decorators like `functools.lru_cache` provide, making the output consistent.
Why wrong: Choosing 2, 0, 0 implies you think `Sample.count` is correctly incremented to 2, but each instance somehow has its own `count` attribute initialized to 0. In the exhibit no `self.count = ...` assignment exists inside `__init__`, so instance attribute lookup finds no key in the instance `__dict__` and falls back to the class attribute. Therefore `a.count` and `b.count` also evaluate to 2, not 0.
B
2
2
2
The class attribute `count` begins at 0, and each call to `__init__` executes `Sample.count += 1`; with two instantiations before any `print`, the shared class variable is exactly 2. Attribute access on an instance first checks that instance's namespace; since neither `a` nor `b` ever assigns `self.count`, both lookups resolve to the same class-level integer. Thus the three printed lines are identical: 2, 2, 2.
C
2
1
1
Why wrong: `2, 1, 1` would require each instance to store its own private counter value of 1, masking the class attribute during instance access. However, the code never creates an instance attribute named `count`, so `a.count` and `b.count` do not perform an instance-level lookup that finds a different value; they traverse up to `Sample.count`. The class attribute is shared and unchanged by instance access, so the second and third lines mirror the class value of 2, not a per-instance 1.
D
0
2
2
Why wrong: This output mistakenly places the first `print(Sample.count)` before the two constructor calls, or assumes incrementing happens after printing. In the actual sequence, both `Sample()` calls run first and each increments the class variable, so the printed class attribute is already 2. The subsequent instance accesses also resolve to that same class attribute, so the first line is 2 and the following lines are also 2, not 0 and then 2.
Refer to the exhibit. What is the effect of using 'from None' in the raise statement?
Exhibit
Traceback (most recent call last):
File "test.py", line 3, in <module>
raise ValueError("Invalid value")
ValueError: Invalid value
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test.py", line 5, in <module>
raise TypeError("Type mismatch") from None
TypeError: Type mismatch
A
It re-raises the original ValueError
Why wrong: Re-raising the original ValueError would require an active exception handler and a bare `raise` statement, which rethrows the exception currently being handled. In this code, a brand-new TypeError is created and raised with `from None`; the original ValueError is neither re-thrown nor propagated. The `from None` clause only controls how the new exception's context is displayed, not which exception is raised.
B
It causes a syntax error
Why wrong: The `raise` statement in Python allows an optional `from` clause, and the keyword `None` is a valid expression after `from`, specifically to indicate context suppression. The Python grammar explicitly supports `raise ... from None`, so no SyntaxError is raised. The misconception arises because `from` is usually followed by an exception object, but `None` is a documented special case.
C
It suppresses the exception chain and only shows the TypeError
Using `from None` in a `raise` statement sets the `__suppress_context__` attribute of the exception to `True`. When the TypeError propagates, Python's default exception handler checks this flag and, because it is true, omits the implicit display of the original ValueError and its traceback. The final output therefore contains only the TypeError, either in the interactive shell or in a captured traceback.
D
It chains the TypeError to the original ValueError
Why wrong: Chaining occurs when `raise ... from cause` assigns the `__cause__` attribute, or when an exception occurs during handling and Python automatically sets `__context__`. With `from None`, Python instead sets `__suppress_context__ = True`, which tells the traceback formatter not to show any context. Thus the TypeError is not chained to the ValueError; the chain is deliberately broken and hidden.
Refer to the exhibit. Which of the following fixes the error?
Exhibit
Error log:
Traceback (most recent call last):
File "test.py", line 3, in <module>
print('Hello' + 5)
TypeError: can only concatenate str (not "int") to str
A
print('Hello' + '5')
Why wrong: This statement is syntactically correct and will print 'Hello5', but it is not the correct answer because the question asks which option fixes the error, and both A and B fix it, so selecting only A is incomplete.
B
print('Hello' + str(5))
Why wrong: Similarly, this statement works correctly by converting the integer 5 to a string, but it is only one of the valid fixes. The correct answer is C, which includes both A and B.
C
Both A and B
Both A and B produce the intended output without error. Option A uses direct string concatenation with a string literal, while option B uses explicit conversion of the integer to a string. Neither causes a TypeError, so both fix the error described in the exhibit.
D
print('Hello' * 5)
Why wrong: This statement uses the repetition operator to repeat the string 'Hello' five times, resulting in 'HelloHelloHelloHelloHello'. While syntactically valid, it does not address the error of concatenating a string and an integer, as it does not involve concatenation with the number 5.
These PCAP practice questions are part of Courseiva's free Python Institute certification practice question bank. Courseiva provides original exam-style PCAP questions with detailed explanations, topic-based practice, mock exams, readiness tracking, and study analytics.