Python Institute PCAP-31-03 Practice Test

Prepare for PCAP: Certified Associate Python Programmer (PCAP-31-03) with free sample questions, a full-length diagnostic, topic drills, timed practice, modules, exceptions, strings, object-oriented programming, comprehensions, lambdas, closures, file handling, and detailed explanations in IT Mastery.

PCAP is Certified Associate Python Programmer, the Python Institute associate route for intermediate Python code reading, object-oriented programming, modules, packages, exceptions, strings, comprehensions, lambdas, generators, closures, and files.

IT Mastery practice for PCAP-31-03 is live now. Use this page to start the web simulator, review the exam snapshot, work through 24 public sample questions, and continue into full IT Mastery practice with the same IT Mastery account on web, iOS, iPadOS, macOS, or Android.

Interactive Practice Center

Start a practice session for PCAP: Certified Associate Python Programmer (PCAP-31-03) below, or open the full app in a new tab. For the best experience, open the full app in a new tab and navigate with swipes/gestures or the mouse wheel—just like on your phone or tablet.

Open Full App in a New Tab

A small set of questions is available for free preview. Subscribers can unlock full access by signing in with the same app-family account they use on web and mobile.

Prefer to practice on your phone or tablet? Download the IT Mastery – AWS, Azure, GCP & CompTIA exam prep app for iOS or IT Mastery app on Google Play (Android) and use the same IT Mastery account across web and mobile.

Free diagnostic: Try the 40-question PCAP-31-03 full-length practice exam before subscribing. Use it as one associate-level Python baseline, then return to IT Mastery for timed mocks, topic drills, explanations, and the full PCAP question bank.

What this PCAP practice page gives you

  • a direct route into IT Mastery practice for PCAP-31-03
  • 24 on-page sample questions with detailed explanations
  • topic drills and mixed sets across modules, exceptions, strings, OOP, comprehensions, lambdas, closures, and files
  • a clear free-preview path before you subscribe
  • the same IT Mastery account across web and mobile

Who PCAP is for

  • Python learners who already know syntax and now need intermediate code-reading practice
  • candidates preparing for object-oriented programming, imports, exceptions, strings, and file-handling questions
  • developers comparing Python certification against Java, GitHub, data, or cloud-developer routes

PCAP exam snapshot

  • Vendor: Python Institute / OpenEDG
  • Official certification name: Certified Associate Python Programmer
  • Exam code / family: PCAP-31-03 / PCAP-31-0x
  • IT Mastery practice bank: 1,836 questions
  • Current IT Mastery status: live practice available

Topic coverage for PCAP practice

DomainWeight
Modules and Packages12%
Exceptions14%
Strings18%
Object-Oriented Programming34%
Miscellaneous - List Comprehensions, Lambdas, Closures, I/O22%

How to use the PCAP simulator efficiently

  1. Start with OOP drills because class behavior, inheritance, overriding, and object relationships carry the largest PCAP weight in this build.
  2. Review import, namespace, exception, and string misses until you can predict the exact runtime behavior.
  3. Move into mixed sets once modules, files, comprehensions, lambdas, closures, and OOP questions stop feeling isolated.
  4. Use timed practice near the end so multi-line code snippets do not slow your pacing.

PCAP Python decision filters

Use these filters when intermediate Python snippets look familiar but behave differently:

  • Object model: trace classes, instances, attributes, methods, inheritance, overriding, MRO, and encapsulation assumptions.
  • Namespace and import: distinguish module scope, local scope, package import behavior, aliases, and name shadowing.
  • Exception path: decide which exception is raised, caught, re-raised, suppressed, or left unhandled.
  • Sequence and string behavior: check indexing, slicing, immutability, methods, formatting, and iteration.
  • Functional and file patterns: trace comprehensions, lambdas, closures, generators, file handles, context managers, and I/O effects.

Final 7-day PCAP practice sequence

DayPractice focus
7Take the free full-length diagnostic and tag misses by modules, exceptions, strings, OOP, or miscellaneous Python behavior.
6Drill modules, packages, imports, namespaces, aliases, and executable module behavior.
5Drill exceptions, traceback flow, custom exceptions, strings, slicing, formatting, and string methods.
4Drill classes, inheritance, attributes, methods, overriding, encapsulation, and object relationships.
3Drill comprehensions, lambdas, closures, file I/O, context managers, and mixed code-tracing snippets.
2Complete a timed mixed set and explain the exact Python runtime or object-model rule behind every miss.
1Review weak snippet patterns; avoid late memorization of unfamiliar library details.

When PCAP practice is enough

If several unseen mixed attempts are above roughly 75% and you can explain the object model, namespace, exception path, or runtime behavior behind each answer, you are likely ready. More practice should improve code-tracing accuracy, not repeated-snippet memory.

Focused sample questions

Use these child pages when you want focused IT Mastery practice before returning to mixed sets and timed mocks.

Free study resources

Need concept review first? Read the PCAP-31-03 Cheat Sheet on Tech Exam Lexicon, then return here for timed mocks, topic drills, and full IT Mastery practice.

Free preview vs premium

  • Free preview: 24 public sample questions on this page plus the web app entry so you can validate the question style and explanation depth.
  • Premium: the full PCAP-31-03 practice bank, focused drills, mixed sets, timed mock exams, detailed explanations, and progress tracking across web and mobile.

Good next pages after PCAP

  • PCEP if you need entry-level Python first
  • PCPP1 when you are moving into professional-level Python design and applied libraries
  • Oracle Java SE 21 1Z0-830 if you are comparing language-certification paths
  • GitHub Actions if your next goal is automation and developer workflow

Official sources

24 PCAP sample questions with detailed explanations

These are original IT Mastery practice questions aligned to the live PCAP-31-03 route and the main blueprint areas shown above. Use them to test readiness here, then continue in IT Mastery with mixed sets, topic drills, and timed mocks.

Question 1

Topic: Section 4: Object-Oriented Programming

A transport application already defines Vehicle, LandVehicle(Vehicle), and WaterVehicle(Vehicle). A new class, AmphibiousVehicle, must inherit behavior from both LandVehicle and WaterVehicle, creating a diamond-shaped hierarchy through Vehicle. Which definition matches this requirement?

  • A. class AmphibiousVehicle(LandVehicle, WaterVehicle): pass
  • B. class AmphibiousVehicle(Vehicle): pass
  • C. class AmphibiousVehicle(LandVehicle): pass
  • D. class AmphibiousVehicle(WaterVehicle): pass

Best answer: A

Explanation: The requirement says the new class must inherit from two existing classes, not just one. In Python, that means multiple inheritance with both base classes listed in the class header. This hierarchy requires multiple inheritance because AmphibiousVehicle must be a subclass of both LandVehicle and WaterVehicle. Since those two classes already inherit from Vehicle, adding AmphibiousVehicle above them forms the classic diamond shape.

In Python, multiple inheritance is declared by listing more than one base class in the class definition:

  • LandVehicle is one direct base.
  • WaterVehicle is the second direct base.
  • Vehicle remains an indirect base through both paths.

Using only one parent class would create single inheritance and would not match the stated hierarchy. The key takeaway is that one child class with two direct parents requires multiple inheritance.


Question 2

Topic: Section 3: Strings

A developer wants to remove only the final extension from an archive name, but this intermediate version stops at the first dot instead of the last.

name = "backup.final.tar.gz"
cut = name.find(".")
print(name[:cut])

It currently prints backup, but the required result is backup.final.tar. Which change is the best fix?

  • A. Replace name.find(".") with name.find(".", 1).
  • B. Replace name.find(".") with name.index(".").
  • C. Replace name.find(".") with name.rfind(".").
  • D. Replace name.find(".") with ".".find(name).

Best answer: C

Explanation: The bug comes from searching in the wrong direction. find() returns the first matching position, but removing only the final extension requires the last dot, so rfind() is the correct fix. When a string can contain several dots, the search method determines which position you slice on. find(".") scans left to right and returns the first dot, so name[:cut] keeps only backup. To remove just the last extension, you need the final dot instead.

name = "backup.final.tar.gz"
cut = name.rfind(".")
print(name[:cut])

rfind() scans from the right and returns the last matching index, so the slice keeps backup.final.tar. Changing only the start position of find() would work only if you already knew where earlier dots ended, and index() still searches from the left. The key idea is that the search direction must match the parsing goal.


Question 3

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 ????

  • 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.


Question 4

Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)

A developer is troubleshooting a list comprehension that should keep only even numbers.

values = [1, 2, 3, 4, 5, 6]
evens = [n if n % 2 == 0 else 0 for n in values]
print(evens)

The program prints [0, 2, 0, 4, 0, 6], but the expected result is [2, 4, 6]. What is the best fix?

  • A. evens = [0 for n in values if n % 2 == 0]
  • B. evens = [n for n in values if n % 2]
  • C. evens = [n for n in values if n % 2 == 0]
  • D. evens = [n if n % 2 == 0 for n in values]

Best answer: C

Explanation: The code uses a conditional expression, so every input element still produces one output element. To remove unwanted values entirely, the condition must appear after the for clause as the list comprehension’s filter. List comprehensions support two different if patterns. In this case, the code uses n if n % 2 == 0 else 0, which is a conditional expression: it keeps even numbers but substitutes 0 for odd numbers, so the result list stays the same length as the input.

To filter values out, use the comprehension filter form:

evens = [n for n in values if n % 2 == 0]

That trailing if decides whether each item is included at all. The key difference is replacement versus omission.


Question 5

Topic: Section 2: Exceptions

A package contains these files:

# billing/__init__.py

# billing/errors.py
class BillingError(Exception):
    pass

class InvalidInvoice(ValueError):
    pass

# billing/loader.py
from .errors import InvalidInvoice

def load_invoice(data):
    if data == "":
        raise InvalidInvoice("empty invoice")

# app.py
from billing.errors import BillingError
from billing.loader import load_invoice

try:
    load_invoice("")
except BillingError:
    print("billing problem")

Running app.py raises an uncaught exception. Which change fixes the problem while keeping the existing except BillingError: design?

  • A. Use import billing.errors and catch billing.errors.BillingError
  • B. Re-export InvalidInvoice from billing/__init__.py
  • C. Delete billing/__pycache__ before rerunning
  • D. Make InvalidInvoice inherit from BillingError

Best answer: D

Explanation: except BillingError only handles BillingError objects and subclasses of it. Here, InvalidInvoice inherits from ValueError, so it is outside that custom hierarchy and is not caught by the handler. The core concept is exception hierarchy matching. In Python, an except SomeError: clause catches instances of SomeError and any subclass below it in the inheritance tree. In this package, BillingError is intended to be the common base for billing-related failures, but InvalidInvoice was defined as a subclass of ValueError instead.

That means the raised exception belongs to the ValueError branch, not the BillingError branch. To integrate the custom exception into the existing hierarchy, redefine it as a subclass of BillingError.

Import style, package exports, and cached bytecode do not change which base class an exception inherits from. The key takeaway is that catch behavior depends on the class hierarchy, not on how the module is imported.


Question 6

Topic: Section 4: Object-Oriented Programming

A developer wants CachedLookupError to belong to two custom exception branches so the same raised object can match either RecoverableError or CacheError, depending on handler order.

class AppError(Exception):
    pass

class RecoverableError(AppError):
    pass

class CacheError(AppError):
    pass

# replace TODO here

try:
    raise CachedLookupError('cache miss')
except RecoverableError as ex:
    print(type(ex).__name__, ex.args[0])
except CacheError:
    print('cache')

Which definition correctly replaces TODO?

  • A. class CachedLookupError(AppError): pass
  • B. class CachedLookupError(RecoverableError, CacheError): pass
  • C. class CachedLookupError(RecoverableError): pass
  • D. class CachedLookupError[RecoverableError, CacheError]: pass

Best answer: B

Explanation: In Python, multiple inheritance is written by listing more than one base class in the class header. Using both RecoverableError and CacheError makes CachedLookupError part of both exception branches, so handler order determines which matching except runs first. The core concept here is Python multiple inheritance syntax: class Name(Base1, Base2):. A class defined that way becomes a subclass of both listed bases. Because RecoverableError and CacheError are both exception classes, CachedLookupError would also participate in both exception branches.

In this example:

  • raising CachedLookupError('cache miss') creates an object of that subclass
  • that object matches both RecoverableError and CacheError
  • except blocks are checked top to bottom
  • the first matching handler runs

So the RecoverableError handler would execute first. A class inheriting only from AppError is too general, and inheriting only from RecoverableError gives just one branch. The key takeaway is that multiple inheritance requires a comma-separated base list inside parentheses.


Question 7

Topic: Section 3: Strings

A developer uses .find() to extract optional fields from a semicolon-separated record. What is printed by this code?

def get_value(line, key):
    start = line.find(key)
    if start == -1:
        return "N/A"
    start += len(key)
    end = line.find(";", start)
    if end == -1:
        end = len(line)
    return line[start:end]

record = "id=42;name=Ana;team=QA"
print(get_value(record, "name=") + "|" + get_value(record, "email="))
  • A. Ana|N/A
  • B. Ana|-1
  • C. name=Ana|N/A
  • D. Ana|

Best answer: A

Explanation: str.find() returns the starting index of a substring, or -1 if the substring is absent. Here, name= is found and produces Ana, while email= is not found, so the function returns the sentinel replacement string N/A. str.find() is useful when missing text should be handled by a sentinel value instead of an exception. It returns the index of the first match, or -1 when the substring is absent. In this function, a -1 result immediately causes "N/A" to be returned.

  • For name=, start points to the beginning of that key.
  • Adding len(key) moves start to the first character of the value, A.
  • The next ; marks the end of the value, so the slice is Ana.
  • For email=, .find() returns -1, so the function does not slice at all and returns N/A.

The main takeaway is that .find() itself returns -1 for absence, but the surrounding code can translate that sentinel into a different result.


Question 8

Topic: Section 1: Modules and Packages

A support script is being reviewed. No host OS details are provided.

import platform

try:
    raise RuntimeError(platform.system())
except RuntimeError as err:
    print(err.args[0])
else:
    print("ok")
finally:
    print("done")

Which statement is correct?

  • A. ok is printed before done
  • B. The first line is the same on every host
  • C. Catching the exception prevents finally from running
  • D. The except block runs, and the exact first line cannot be predicted from the code alone

Best answer: D

Explanation: The script always raises a RuntimeError, so the except RuntimeError as err block runs and else is skipped. However, err.args[0] comes from platform.system(), and without host details, that exact text cannot be known in advance. This question combines deterministic exception flow with a host-dependent API. The raise RuntimeError(platform.system()) statement always creates and raises a RuntimeError, so control always moves to the matching except RuntimeError as err block. The exception object stores the argument passed to it, so err.args[0] is the result of platform.system().

Because platform.system() reports information about the current machine, its exact value depends on the host environment. Without those host details, you cannot predict the exact first line. The else clause runs only when no exception occurs, and the finally clause runs whether the exception was handled or not.

The key takeaway is that exception flow here is fixed, but the platform string is not.


Question 9

Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)

In a package-based project, the script below imports a helper and reads two lines from a text file. To follow proper handle usage, where should f.close() be added?

from tools.paths import get_name

f = open(get_name(), 'r')
print(f.readline().strip())
print(f.readline().strip())
  • A. Before the first print() call
  • B. After the second print() call
  • C. Between the two print() calls
  • D. No explicit close() is needed

Best answer: B

Explanation: A stream or file handle should be closed after its last required operation. Here, both print() calls read from f, so closing earlier would break the second read, and the package import does not manage the file handle for you. The core idea is to close a stream when your code has finished using it. In this snippet, f is still needed for both readline() calls because each print() reads from the same open file. That makes the first safe place to call f.close() the point immediately after the second print() completes.

Closing the file before that would leave the next readline() operating on a closed stream, which is invalid. The imported function get_name() only supplies the filename; importing it from a package does not automatically close files opened by the caller. Good practice is to release the handle as soon as the last file operation is done.


Question 10

Topic: Section 2: Exceptions

A developer wants all application-specific errors to be caught by except AppError:. In this program, ConfigError was meant to be part of that hierarchy.

class AppError(Exception):
    pass

class ConfigError(Exception):
    pass

def load_config():
    raise ConfigError("missing file")

try:
    load_config()
except AppError:
    print("handled")
finally:
    print("done")

What happens when the code runs?

  • A. It prints handled and then done.
  • B. It prints done and then raises an uncaught ConfigError.
  • C. It prints done and then raises an uncaught AppError.
  • D. It raises ConfigError before finally can print done.

Best answer: B

Explanation: Exception matching depends on inheritance, not on the class name or the developer’s intent. Because ConfigError does not inherit from AppError, the handler is skipped, finally runs, and the original ConfigError propagates. In Python, an except SomeError: clause catches exceptions that are instances of SomeError or its subclasses. Here, AppError is a custom base exception, but ConfigError incorrectly inherits from Exception instead of AppError.

When load_config() raises ConfigError, Python checks except AppError: and finds no match. The finally block still executes, so done is printed. After that, the same ConfigError continues upward as an unhandled exception.

The key fix is to define ConfigError as a subclass of AppError, not merely as another custom exception.


Question 11

Topic: Section 4: Object-Oriented Programming

A developer is testing a method that should update an instance attribute. What happens when this Python 3 code runs?

class Counter:
    def __init__(self, start):
        self.value = start

    def add(step):
        self.value += step

c = Counter(10)
c.add(5)
print(c.value)
  • A. It prints 15
  • B. It prints 10
  • C. It raises TypeError on c.add(5)
  • D. It raises NameError on self.value += step

Best answer: C

Explanation: Instance methods must declare self as the first parameter. When c.add(5) is called, Python automatically passes the instance and then 5, so add(step) receives too many arguments and raises TypeError before any update occurs. In Python, a function accessed through an instance becomes a bound method. That means the instance is supplied automatically as the first argument during the call. Here, c.add(5) effectively tries to call add(c, 5).

But the method was defined as def add(step):, so it accepts only one positional argument. Because the call provides two, Python raises TypeError immediately. The line self.value += step is never executed, so print(c.value) is never reached. If the method were defined as def add(self, step):, then the instance would bind to self, 5 would bind to step, and the value would become 15.


Question 12

Topic: Section 3: Strings

A developer is checking how indexing and slicing return string values. What is printed by the following Python 3 code?

s = "window"
print("[" + s[2] + "][" + s[2:3] + "][" + s[2:] + "]")
  • A. [n][ndow][ndow]
  • B. [n][n][ndow]
  • C. [ndow][n][ndow]
  • D. [n][n][ndo]

Best answer: B

Explanation: String indexing returns one character, while string slicing returns a substring. Here, s[2] gives "n", s[2:3] also gives "n" because that slice covers one position, and s[2:] gives "ndow". In Python, both indexing and slicing on a string return values of type str, but they mean different things. A single index like s[2] returns exactly one character. A slice like s[2:3] returns the substring starting at index 2 and stopping before index 3, so it also contains just one character: "n".

The last expression, s[2:], is a slice with no stop value, so it returns every character from index 2 through the end of the string, which is "ndow". That makes the printed result [n][n][ndow]. A common mistake is to treat s[2:3] as if it returned the rest of the string instead of a one-character substring.


Question 13

Topic: Section 1: Modules and Packages

A developer keeps a reusable class in widget.py and a runner script in app.py.

# widget.py
class Widget:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f"Widget({self.name})"

print("loaded:", __name__)

if __name__ == "__main__":
    print(Widget("demo"))
# app.py
import widget
print(widget.Widget("main"))

If app.py is executed, what is the output?

  • A. loaded: main Widget(demo) Widget(main)
  • B. loaded: widget Widget(demo) Widget(main)
  • C. Widget(main)
  • D. loaded: widget Widget(main)

Best answer: D

Explanation: When app.py imports widget, Python executes widget.py once with __name__ equal to the module name widget. That prints loaded: widget, skips the guarded demo block, and then app.py prints the Widget("main") object using __str__(). The key idea is that __name__ changes based on how a file is used. If a file is run directly, its __name__ is "__main__". If it is imported, its __name__ is the module name instead.

Here, app.py imports widget, so Python executes the top-level statements in widget.py during the import. That means print("loaded:", __name__) runs and prints loaded: widget. However, the if __name__ == "__main__": block does not run, because widget.py is being imported, not executed as the main script. After the import, app.py prints widget.Widget("main"), and Widget.__str__() returns Widget(main).

The main trap is thinking that imported modules skip all top-level code; only the code inside the __name__ == "__main__" guard is skipped.


Question 14

Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)

Assume report.bin already exists and contains exactly the three ASCII bytes ABC. What happens when this Python 3 code runs?

with open("report.bin", "rb+") as f:
    first = f.read(1)
    f.write("Z")
    print(first)
  • A. It prints A, then the file becomes AZC.
  • B. It prints b'A', then the file becomes AZC.
  • C. It raises TypeError because read(1) in binary mode returns str.
  • D. It raises TypeError, and the file remains ABC.

Best answer: D

Explanation: rb+ means read/write in binary mode. In binary mode, read() returns bytes, but write() also expects bytes, so writing the string "Z" raises TypeError before anything is printed. The core concept is that text mode works with str, while binary mode works with bytes. Because the file is opened with rb+, f.read(1) returns b'A', not 'A'. However, f.write("Z") tries to write a Python string to a binary stream, and Python 3 does not implicitly encode that string to bytes.

That mismatch raises TypeError immediately, so execution never reaches print(first). Since the write does not succeed, the file content stays ABC.

If the file had been opened in text update mode such as r+, then reading would return 'A' and writing "Z" would be valid.


Question 15

Topic: Section 2: Exceptions

A developer adds a custom validation exception to the ValueError hierarchy. What is the output of this code?

class DataError(ValueError):
    pass

def parse(text):
    print("try parse")
    if not text.isdigit():
        raise DataError("bad data")
    print("parsed")

try:
    parse("3x")
except ValueError:
    print("value handler")
except DataError:
    print("data handler")
finally:
    print("cleanup")
  • A. try parse data handler cleanup
  • B. try parse cleanup value handler
  • C. try parse parsed cleanup
  • D. try parse value handler cleanup

Best answer: D

Explanation: DataError is a custom exception derived from ValueError. When parse("3x") raises it, Python uses the first compatible except clause, which is except ValueError, and then always runs the finally block. DataError is integrated into the existing exception hierarchy by subclassing ValueError, so any handler for ValueError can also catch DataError. In parse("3x"), the first print runs, then "3x".isdigit() returns False, so DataError is raised before print("parsed") can execute. Python checks except clauses from top to bottom and stops at the first one that matches. Because DataError is a kind of ValueError, the except ValueError block handles it, and the later except DataError block is never reached. After the handler finishes, the finally block always runs, so cleanup prints last. The closest distractor is the one with data handler, but that would require the except DataError block to appear before except ValueError.


Question 16

Topic: Section 4: Object-Oriented Programming

A developer is debugging where attributes are stored and runs this code:

class Report:
    status = "draft"

    def __init__(self, name):
        self.name = name

r = Report("Q1")

try:
    print(r.__dict__["status"])
except KeyError as err:
    print("missing:", err.args[0])
    print("class:", Report.__dict__["status"])
else:
    print("instance")
finally:
    print("keys:", sorted(r.__dict__.keys()))

Which statement about the execution is correct?

  • A. The except block runs because status is absent from r.__dict__ but present in Report.__dict__.
  • B. The else block runs because inherited class attributes appear in r.__dict__.
  • C. The first lookup raises TypeError because __dict__ is not subscriptable.
  • D. The handled KeyError prevents the finally block from running.

Best answer: A

Explanation: An instance __dict__ contains only attributes stored directly on that object, so r.__dict__ has name but not the class variable status. That missing key raises KeyError, the handler reads status from Report.__dict__, and finally still runs. The key concept is the difference between attribute lookup and namespace storage. r.status would work because Python searches the instance first and then the class, but r.__dict__["status"] checks only the instance namespace. In this code, r.__dict__ contains just the instance attribute name, so indexing it with "status" raises KeyError.

The except block handles that error and uses Report.__dict__["status"], which succeeds because status is a class attribute stored in the class namespace. The else block is skipped because an exception did occur, and the finally block executes regardless.

So the important distinction is where the data is stored, not whether normal dotted attribute access can find it.


Question 17

Topic: Section 3: Strings

A developer is checking how escape sequences in a string literal become characters at runtime. What is printed by this code?

msg = "A\\nB\nC"
print(msg)
print(len(msg))
  • A. text A B C 5
  • B. text A\\nB C 5
  • C. text A\\nB\\nC 7
  • D. text A\\nB C 6

Best answer: D

Explanation: Python interprets escape sequences while reading the string literal. In "A\\nB\nC", \\ produces a literal backslash, so \ becomes the two characters \ and n, while the later becomes a real newline. Escape sequences are part of the source-code representation of a string literal. Python converts them into characters when the program is parsed. In this example, \\ becomes one backslash character, so the first \ does not become a newline; it becomes the two runtime characters \ and n. The second is a real newline character.

That means the runtime string contains 6 characters: A, \, n, B, newline, and C. So print(msg) displays A\nB on the first line and C on the second line, and print(len(msg)) then prints 6.

The closest mistake is to treat \ as if it were another newline escape.


Question 18

Topic: Section 1: Modules and Packages

A developer creates calc.py with the following code:

total = 10
_tax = 2

def show():
    return total + _tax

In another file, they run:

from calc import *
print(total)
print(_tax)

Which statement best describes the result?

  • A. 10 prints, then NameError occurs for _tax.
  • B. Both lines print because all module variables are imported.
  • C. A SyntaxError occurs because _tax is not a valid name.
  • D. Nothing prints because wildcard import brings in functions only.

Best answer: A

Explanation: A single leading underscore marks a module name as non-public by convention. With from calc import *, total is imported, but _tax is skipped, so the first print() works and the second raises NameError. In Python modules, a leading underscore is the usual naming convention for a non-public name. That does not make the variable truly inaccessible, but it signals that the name is internal to the module.

When wildcard import is used, Python imports public names by default. In this case:

  • total is public, so it is imported.
  • _tax starts with _, so it is not imported.
  • print(total) outputs 10.
  • print(_tax) fails with NameError in the importing file.

The key idea is that module “private” names are private by convention, and wildcard import respects that convention.


Question 19

Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)

A teammate is writing a small reporting helper that should accept a lambda and apply it to each value. The code is supposed to print [15, 25, 35], but it does not.

def process(values, op):
    return [op for v in values]

scores = [10, 20, 30]
print(process(scores, lambda x: x + 5))

Which change is the best fix?

  • A. Replace the return line with return [lambda v: op(v) for v in values].
  • B. Replace the return line with return list(map(op(values), values)).
  • C. Replace the return line with return [op(v) for v in values].
  • D. Replace the return line with return [op(values) for v in values].

Best answer: C

Explanation: process receives op as a callable, so it must invoke that callable for each item. Using op(v) applies the lambda to each score and builds the expected list. Returning op itself does not execute it. A lambda is just a function object, so passing it into a user-defined function works like passing any other callable. The receiving function must call that callable with the current element. Here, op stores the lambda and v is each score from values, so the correct operation is op(v) inside the comprehension.

  • op is the passed function.
  • v is one element from values.
  • op(v) runs the lambda for that element.
  • The comprehension collects the results into a new list.

The broken version repeats the function object itself, so it produces a list of callables instead of transformed numbers.


Question 20

Topic: Section 2: Exceptions

Assume the program runs with assertions enabled. A developer validates a 4-character code after removing dashes. What is printed?

code = "12-3a"
cleaned = "".join([ch for ch in code if ch != "-"])

try:
    assert cleaned.isdigit() and len(cleaned) == 4, cleaned
    print("valid")
except AssertionError as e:
    print("assert:", e)

print("done")
  • A. assert: 123a done
  • B. valid done
  • C. assert: 123a valid done
  • D. AssertionError: 123a

Best answer: A

Explanation: assert condition, value raises AssertionError(value) when the condition is false. After the dashes are removed, the string is 123a, so .isdigit() is false; the exception is caught, its message is printed, and then done is printed. The core concept is that assert x, y behaves like: if x is false, raise AssertionError(y). In this code, the list comprehension removes only -, so cleaned becomes 123a. Its length is 4, but 123a.isdigit() returns False, so the whole condition after assert is false.

Python therefore raises AssertionError('123a'). The except AssertionError as e block catches that exception and prints assert: 123a. Because the exception is handled, execution continues normally to the final print("done") statement.

A traceback is not shown here because the matching except block handles the assertion failure.


Question 21

Topic: Section 4: Object-Oriented Programming

A developer wants to check which attributes belong to an object instance. What is printed by this Python 3 code?

class Box:
    kind = "tool"

    def __init__(self):
        self.size = 10
        self.__code = 99

b = Box()
b.count = 3
print(sorted(b.__dict__.keys()))
  • A. ['__code', 'count', 'size']
  • B. ['_Box__code', 'count', 'kind', 'size']
  • C. ['_Box__code', 'count', 'size']
  • D. ['count', 'kind', 'size']

Best answer: C

Explanation: __dict__ on an instance shows that instance’s own attributes, not class variables. The attribute __code is name-mangled inside the instance dictionary, so its key appears as _Box__code. An object’s __dict__ stores its instance attributes as key-value pairs. In this code, size is created in __init__, count is added later to the same instance, and __code is a private-style attribute. Python applies name mangling to __code, so the actual key saved in the instance dictionary is _Box__code.

kind is different: it is a class variable, stored on Box, not in b.__dict__. Because the code prints sorted(b.__dict__.keys()), the result is an alphabetically sorted list of only the instance-level keys.

The key takeaway is that inspecting obj.__dict__ reveals the object’s own attributes, including mangled private names, but not class attributes.


Question 22

Topic: Section 3: Strings

A developer is adding an instance method to this class. The method must search the object’s text attribute for a substring and return the first position, or -1 when the substring is missing. Which implementation meets the requirement?

class Note:
    def __init__(self, text):
        self.text = text

    def find_marker(self, marker):
        pass
  • A. return self.text.index(marker)
  • B. return self.text.find(marker)
  • C. return self.text.find(marker) or -1
  • D. return 0 if marker not in self.text else self.text.find(marker)

Best answer: B

Explanation: self.text.find(marker) exactly matches the method contract. str.find() returns the starting index of the first match and uses -1 as the sentinel when nothing is found. str.find() is the string-search method used when a missing substring should not raise an exception. In this instance method, self.text is the object’s string attribute, so self.text.find(marker) searches that stored value directly.

  • If the substring starts at position 5, the method returns 5.
  • If the substring starts at position 0, the method returns 0.
  • If the substring is absent, the method returns -1.

That behavior is why find() is preferred here over index(), which raises ValueError when the substring is missing. It is also why boolean shortcuts are risky: position 0 is a valid result, but it is falsy in Python.


Question 23

Topic: Section 1: Modules and Packages

A project contains this package:

store/
    __init__.py
    reports/
        __init__.py
        daily.py   # def count(): return 3

Assume the import succeeds. Which expression should replace ??? so that the except block is skipped and the program prints 3, then ok, then done?

import store.reports.daily

try:
    print(???)
except (NameError, AttributeError) as err:
    print("bad ref")
else:
    print("ok")
finally:
    print("done")
  • A. daily.count()
  • B. reports.daily.count()
  • C. store.reports.daily.count()
  • D. store.daily.count()

Best answer: C

Explanation: With import store.reports.daily, Python binds store in the current namespace, not reports or daily by themselves. The call must therefore use the full qualified path store.reports.daily.count(), which lets the try block succeed so else and finally both run. The key concept is how import package.subpackage.module populates the current namespace. That form makes the top-level package name available directly, and nested modules are then accessed as attributes beneath it. In this case, store is the starting name, so the function must be referenced as store.reports.daily.count().

If that expression is used, count() returns 3, so no exception is raised. Because the try block completes normally, the else block prints ok, and the finally block always prints done.

A common mistake is assuming that reports or daily become standalone names automatically; they do not with this import form.


Question 24

Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)

A developer is updating tools/iohelper.py. The code must read 4 characters from a text file and store the same content in a binary file as UTF-8 bytes. Which replacement for ??? is correct?

with open("report.txt", "rt") as src, open("head.bin", "wb") as dst:
    data = src.read(4)
    dst.write(???)
  • A. data
  • B. data.encode("utf-8")
  • C. data.decode("utf-8")
  • D. str(data)

Best answer: B

Explanation: Text mode reads produce str objects, while binary mode writes require bytes-like data. Because the code reads from "rt" and writes to "wb", the string must be converted with encode("utf-8") before writing. In Python, text mode and binary mode expect different data types. A file opened with "rt" returns text, so src.read(4) produces a str. A file opened with "wb" writes raw bytes, so dst.write() expects a bytes object.

data.encode("utf-8") converts the four-character string into its UTF-8 byte representation, which matches the binary stream. That is why the code runs correctly and writes the intended content to head.bin.

The key takeaway is simple: text streams work with str, and binary streams work with bytes. When moving data from one mode to the other, you usually need encode() or decode() in the correct direction.

PCAP Python associate map

Use this map after the sample questions to connect individual items to the intermediate Python OOP, exceptions, modules, iterators, and file-processing decisions these practice samples test.

    flowchart LR
	  S1["Analyze code or design need"] --> S2
	  S2["Resolve classes objects and inheritance"] --> S3
	  S3["Trace exceptions iterators and generators"] --> S4
	  S4["Apply file module or package behavior"] --> S5
	  S5["Predict output side effect or error"] --> S6
	  S6["Choose maintainable Python answer"]

Quick Cheat Sheet

CueWhat to remember
OOPTrack classes, instances, attributes, methods, inheritance, overriding, and special methods.
ExceptionsUnderstand try/except/else/finally flow and exception hierarchy.
IteratorsKnow iterable protocol, generators, yield, and lazy evaluation.
FilesUse context managers, modes, encoding, and read/write behavior carefully.
ModulesReview imports, packages, namespaces, and standard-library expectations.

Mini Glossary

  • Generator: Function or expression that yields values lazily.
  • Context manager: Object used with with to manage setup and cleanup.
  • Inheritance: Class relationship where a subclass reuses or specializes a base class.
  • Namespace: Mapping from names to objects.
  • Special method: Double-underscore method such as __iter__ or __str__ used by Python protocols.

In this section

Revised on Friday, May 15, 2026