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.
The attribute 'age' is not defined in the class or instance.
The attribute lookup for 'age' fails because it was never set on the instance or the class. When an object attribute is accessed, Python first checks the instance's __dict__, then the class and its base classes; since neither 'self.age' in __init__ nor a class-level 'age' exists, AttributeError is raised. The instance only has 'name' assigned, so any other attribute name, including 'age', is undefined.
B
The __init__ method was not called.
Why it fails: The __init__ method is invoked automatically at instance creation, so it was indeed called. This is proven by the fact that the instance has the 'name' attribute, which is assigned inside __init__. The error is not about the method being skipped, but about __init__ not defining an 'age' attribute; even though the method ran, it only assigned 'name', leaving 'age' undefined.
C
The attribute 'age' is private.
Why it fails: An attribute is private in Python only when its name begins with two underscores (e.g., __age), which triggers name mangling to _ClassName__age. The simple attribute 'age' has no leading underscores, so Python treats it as a normal, public attribute. Thus, privacy has nothing to do with this error; the lack of definition is the sole issue.
D
The attribute 'age' is a class attribute but not initialized.
Why it fails: A class attribute is a variable defined directly in the class body, not inside __init__ or a method, and it would be shared across all instances. Here, neither the class nor the instance contains a definition for 'age', so it cannot be a class attribute that exists but is uninitialized. If a class attribute were merely uninitialized (without a value), Python would still raise an AttributeError at access time, but that is not the situation in the given code—there is no such attribute at all.
Refer to the exhibit. If the file config.cfg exists but the user does not have read permission, what will be printed?
Exhibit
try:
with open('config.cfg', 'r') as f:
data = f.read()
except FileNotFoundError:
print('File not found')
except PermissionError:
print('Permission denied')
A
An unhandled exception is raised
Why it fails: An unhandled exception cannot occur because the enclosing try block explicitly includes an `except PermissionError` clause. Once the runtime raises PermissionError, the handler is matched and the exception is caught, preventing propagation to the top level. Thus no traceback or unhandled-error termination occurs.
B
'Permission denied'
Since config.cfg exists but the process lacks the required read permission, the file's opening operation raises `PermissionError`. The corresponding `except PermissionError` clause catches it and executes its print statement, producing `'Permission denied'` as the program's visible output. This is the expected, handled outcome.
C
Nothing; the program continues silently
Why it fails: The exception handler does not contain a bare `pass` or suppress the error; it contains a `print('Permission denied')` call. Therefore the program produces output and does not silently continue, because control flows through the handler and prints to the console rather than skipping any visible side effect.
D
'File not found'
Why it fails: `FileNotFoundError` is raised only when the target file is missing from the filesystem. In this scenario the file exists, so the `except FileNotFoundError` branch is never entered; attempting to open it with no read permission condition instead raises `PermissionError`. These are distinct subclasses of `OSError`, so only the matching handler runs.
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 it fails: 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 it fails: 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 it fails: 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.
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 it fails: 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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. 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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 it fails: 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.
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 it fails: 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 it fails: 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 it fails: 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.
Refer to the exhibit. A Python script uses the following code to load the policy. However, it fails with a JSONDecodeError. What is the most likely cause?
```python
The JSON file contains a trailing comma after the last element.
The correct diagnosis: Python's json.load() is a strict parser that follows RFC 8259, and a trailing comma after the last element is not valid JSON syntax. The file likely contains something like [{"a":1},{"b":2},], which causes the parser to encounter a comma before the closing bracket and raise JSONDecodeError with a message such as 'Expecting value'. No amount of variable handling or file mode changes can repair that malformed structure.
B
The policy variable is used before assignment elsewhere in the script.
Why it fails: This is wrong because the traceback points to json.load(), meaning the exception is raised inside the JSON parser while it is reading and decoding the file contents. At that point, the Python interpreter has not yet executed any assignment to the 'policy' variable, so a use-before-assignment error elsewhere is irrelevant. An UnboundLocalError would only occur when the script later tries to access 'policy', and it would occur after json.load() returns successfully, not during the parsing step.
C
The file should be opened in binary mode ('rb') instead of text mode.
Why it fails: Opening the file in binary mode ('rb') does not fix or cause a JSONDecodeError. JSON text is valid input passed as either str from a text-mode file or bytes from a binary-mode file, since json.loads() accepts both str and bytes and decodes them according to the UTF-8 default. The error here is a syntax-level violation within the JSON data itself, not a character-encoding or file-open-mode issue, so switching to 'rb' would leave the trailing comma and still raise the same JSONDecodeError.
D
The JSON decoder cannot handle IP addresses in strings.
Why it fails: IP addresses stored as strings in JSON are completely valid—they are simply character sequences inside double quotes, such as "192.168.1.1" or "::1". The JSON decoder parses string values by reading escaped characters and Unicode, and it does not validate or reject textual content like IP addresses. A JSONDecodeError mentioning an IP address would only appear if the address were unquoted (e.g., a bare 192.168.1.1), which is a missing-quotes syntax issue, not a limitation of the decoder.
Refer to the exhibit.
name = "Alice"
age = 30
print(f"{name} is {age} years old.")
A
{name} is {age} years old.
Why it fails: This output would occur only if the string were written without the f prefix, making it a regular string literal where curly braces have no special meaning and are printed as-is. Since the exhibit uses an f-string (f'...'), the expressions {name} and {age} are evaluated and replaced with their values. Thus, this is not what the code actually prints.
B
Alice is 30 years old.
The f-string's placeholders are evaluated at runtime: {name} is replaced by the value of the variable name, which is 'Alice', and {age} is replaced by the value of age, which is 30. The resulting concatenated string matches exactly this output, making it the correct answer.
C
name is age years old.
Why it fails: This would be the output if the placeholders were written as literal words without curly braces, so the f-string would treat them as plain text. However, the exhibit uses {name} and {age}, which are expressions whose values ('Alice' and 30) are substituted. The variable name 'age' is not printed; its value 30 is.
D
30 is Alice years old.
Why it fails: The f-string processes placeholders left-to-right: {name} first, then {age}. Reversing them would produce '30 is Alice years old', but the code does not reorder the variables. The original sequence places 'Alice' before 30, so this output is impossible given the exhibit's string.
Refer to the exhibit. A developer runs 'pip install pandas==2.0.0' but gets an error stating that the required version is not available. Which command should be used to list all available versions of pandas?
Exhibit
Exhibit:
$ pip list
Package Version
---------- -------
numpy 1.24.0
pandas 1.5.3
pip 22.0.4
A
pip index versions pandas
pip index versions pandas is correct because it contacts the configured package index (PyPI by default) and fetches the release history for the pandas project, listing every available version in ascending order. This is the only option that actually asks the remote index about versions, and it is the pip-native way to check which releases exist before installing or pinning one. Note that this subcommand is experimental, but it is still the right choice among the four.
B
pip search pandas
Why it fails: pip search pandas is wrong because the search subcommand was removed from pip in version 21.0; it relied on PyPI's legacy XML-RPC search API, which is no longer supported. Even in older pip versions it only matched project names and descriptions containing 'pandas', not the list of releases, so it could never produce a version listing. Modern pip will reject the command as unknown, rather than showing anything useful.
C
pip show pandas
Why it fails: pip show pandas is wrong because it displays metadata for a pandas distribution already installed in the current environment, such as its version, location, and dependencies, by reading from site-packages. It does not contact any package index, so it has no information about the versions that could be installed. If pandas has not been installed, pip show simply exits with an error saying that no package was found.
D
pip list --all pandas
Why it fails: pip list --all pandas is an invalid invocation because pip list does not support an --all flag and does not accept a package name as a positional argument; its purpose is to enumerate the installed distributions in the environment. The --all option also does not exist for this command in modern pip, so pip will fail with an unrecognized-options error. Even with valid flags, pip list only reports local packages, so it cannot reveal the version history available on PyPI.
Refer to the exhibit. What happens when the last line is executed?
Exhibit
class MyClass:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
obj = MyClass('John', 30)
obj.city = 'New York'
A
The attribute 'city' is added dynamically.
Why it fails: The attribute 'city' is not added dynamically because __slots__ disables the creation of instance attributes that are not explicitly listed in the __slots__ declaration. In a class with __slots__, the instance has a fixed set of attributes, and any attempt to assign a new attribute such as 'city' raises an AttributeError instead of creating a new __dict__ entry.
B
The __slots__ is ignored because __init__ is defined.
Why it fails: Defining an __init__ method does not cause __slots__ to be ignored. The __slots__ descriptor is part of the class definition and applies to all instances regardless of how they are initialized. The __init__ method runs normally, but the assignment self.city = 'Paris' is still subject to the attribute restrictions imposed by __slots__, so it fails at runtime.
C
A syntax error occurs.
Why it fails: The code does not contain a syntax error; it parses and compiles successfully. The failure occurs only at execution time when the assignment self.city = 'Paris' is attempted on an instance whose class defines __slots__ without 'city'. A syntax error would be detected before any code runs, but here the error is a runtime AttributeError.
D
An AttributeError is raised.
An AttributeError is raised because the class defines __slots__ = ('name', 'age'), and 'city' is not among the declared slots. Python then prevents the assignment of a new attribute, as instances with __slots__ do not have a __dict__ and cannot create undeclared attributes. This is the correct and intended behavior of __slots__.
Refer to the exhibit. Which of the following correctly shows the MRO of class D?
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())
A
[D, B, C, A, object]
This is the exact output of C3 linearization for a diamond hierarchy where D inherits from B and C, and both B and C inherit from A. The merge step first takes D, then B because it is the head of the first base list and appears in no other list's tail, then C (not A, because A is in the tail of C's list), and finally A and object. This preserves both local precedence (B before C) and monotonicity (each base's own MRO is a subsequence of D's).
B
[D, A, B, C, object]
Why it fails: This order places A before B, even though B is a direct subclass of A and appears earlier in its own MRO. C3 requires that every class appear after its subclasses, and it also requires monotonicity: D's MRO must preserve the order B → A from B's MRO. By putting A before B, this ordering violates both constraints, so the merge algorithm would reject it.
C
[D, B, A, C, object]
Why it fails: Here B appears before A, which satisfies B's linearization, but C is erroneously placed after A. Since C is a subclass of A, C must precede A in any valid MRO, and C's own MRO (C, A, object) must remain a subsequence. The C3 merge would never select A before C while C is still available, because A is in the tail of C's list and therefore blocked until C is consumed.
D
[D, C, B, A, object]
Why it fails: This ordering swaps the direct bases of D, placing C before B even though D is declared as class D(B, C). The 'local precedence order' rule in C3 strictly preserves the left-to-right order of base classes in the class statement. Reversing B and C violates that rule, even though the rest of the chain (A, object) is correct, so this would not be produced by Python's MRO.
Why it fails: This response omits the salary attribute entirely, as if it had never been assigned. However, the assignment inside the class does create an instance attribute; name mangling simply changes the stored key to '_Employee__salary'. Since the last line prints the instance's __dict__, the output must include that mangled entry, so a dict containing only 'name' is incomplete and incorrect.
B
AttributeError: 'Employee' object has no attribute '__salary'
Why it fails: An AttributeError would occur only if you directly accessed ex9myx.__salary from outside the class, because Python rewrites that attribute access to ex9myx._Employee__salary. The attribute exists under its mangled name, and printing the instance's __dict__ does not trigger any attribute lookup that raises an error. Thus, the actual output is a dict, not an exception, and the error message about 'no attribute __salary' is misleading because the key is stored as '_Employee__salary'.
C
{'name': 'John', '__salary': 50000}
Why it fails: This output displays '__salary' unchanged, which would only happen if Python did not mangle names inside class bodies. In reality, the compiler rewrites __salary to _Employee__salary before executing the assignment, and that mangled string becomes the real key in the instance dictionary. Consequently, the printed dict cannot contain a literal '__salary' key; it must show the mangled form.
D
{'name': 'John', '_Employee__salary': 50000}
Name mangling transforms __salary into _Employee__salary at compile time, and this mangled name becomes the actual key in the instance's __dict__. Therefore, when the last line prints ex9myx.__dict__, it outputs {'name': 'John', '_Employee__salary': 50000}. This is exactly how Python implements pseudo-private attributes, and it is the correct and expected output for this code.
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.