Try 40 free PCAP-31-03 questions across the exam domains, with explanations, then continue with full IT Mastery practice.
This free full-length PCAP-31-03 practice exam includes 40 original IT Mastery questions across the exam domains.
These questions are for self-assessment. They are not official exam questions and do not imply affiliation with the exam sponsor.
Count note: this page uses the full-length practice count maintained in the Mastery exam catalog. Some certification vendors publish total questions, scored questions, duration, or unscored/pretest-item rules differently; always confirm exam-day rules with the sponsor.
Need concept review first? Read the PCAP-31-03 Cheat Sheet on Tech Exam Lexicon, then return here for timed mocks and full IT Mastery practice.
Open the matching IT Mastery practice page for timed mocks, topic drills, progress tracking, explanations, and full practice.
| Domain | Weight |
|---|---|
| Section 1: Modules and Packages | 12% |
| Section 2: Exceptions | 14% |
| Section 3: Strings | 18% |
| Section 4: Object-Oriented Programming | 34% |
| Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O) | 22% |
Use this as one diagnostic run. IT Mastery gives you timed mocks, topic drills, analytics, code-reading practice where relevant, and full practice.
Topic: Section 4: Object-Oriented Programming
In a fleet-tracking application, a developer defines these classes:
class Vehicle:
pass
class Car(Vehicle):
pass
class Bike(Vehicle):
pass
Which TWO statements correctly identify the superclass/subclass relationships?
Options:
A. Bike is a subclass of Vehicle.
B. Bike is a superclass of Car.
C. Vehicle is a superclass of Car.
D. Vehicle is a subclass of Bike.
E. Car is a superclass of Vehicle.
F. Car inherits directly from Bike.
Correct answers: A and C
Explanation: In Python inheritance syntax, the class being created is written before the parentheses, and the parent class is written inside them. So Car(Vehicle) makes Car a subclass of Vehicle, and Bike(Vehicle) makes Bike a subclass of Vehicle.
The core rule is class Child(Parent):. The name before the parentheses is the new class, and the name inside the parentheses is the direct superclass, also called the base class. In this snippet, both Car and Bike are defined by inheriting from Vehicle, so Vehicle is the superclass for each of them.
class Car(Vehicle): means Car is a subclass of Vehicle.class Bike(Vehicle): means Bike is a subclass of Vehicle.A common mistake is to reverse the relationship or assume that two classes with the same parent inherit from each other.
Car the superclass of Vehicle flips child and parent.Vehicle a subclass of Bike misreads the inheritance syntax.Car inherits from Bike is wrong because both inherit from Vehicle.Bike the superclass of Car treats sibling classes as if one inherited from the other.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
A maintenance script already has this open text stream, and each option is evaluated independently:
stream = open("notes.txt", "rt")
From the current position, exactly 12 characters remain in the file, spread across two lines. The developer wants data to contain all remaining characters by using the read method. Which TWO statements satisfy the requirement?
Options:
A. data = stream.read(12)
B. data = stream.read()
C. data = stream.read(11)
D. data = stream.readline()
E. data = stream.read
F. data = stream.readall()
Correct answers: A and B
Explanation: The read method retrieves data from an opened readable stream. With no argument it returns all remaining content, and with an integer it returns up to that many characters, so read(12) also works here because the stem says exactly 12 characters remain.
read() is the standard file-object method for getting data from a readable stream. When called with no argument, it returns everything from the current cursor position to the end of the file. When called with an integer in text mode, it returns up to that many characters.
Because the stem states that exactly 12 characters remain, both of these return the full remaining contents:
stream.read()stream.read(12)A smaller size reads only part of the file. The fact that the remaining content spans two lines matters because readline() stops after the next line break, not after all remaining data. Also, stream.read without parentheses is just a method reference, not a read operation.
read(11) leaves one character unread.readline() stops at the next newline, so it does not guarantee all remaining content.stream.read does not call the method.readall() is not the normal file-object API in this PCAP context.Topic: Section 4: Object-Oriented Programming
A developer is reviewing this class design:
class Device:
def status(self):
return "device"
class Printer(Device):
def status(self):
return "printer"
class Scanner(Device):
def status(self):
return "scanner"
class AllInOne(Printer, Scanner):
pass
The team says this diamond-shaped inheritance is harder to reason about than single inheritance. Which TWO statements correctly explain why?
Options:
A. A diamond hierarchy prevents subclasses from overriding inherited methods.
B. Python automatically calls both parent versions of status() for one method call.
C. You must know the method resolution order to predict which parent method is used.
D. The shared base class is reached through more than one inheritance path.
E. Method lookup becomes random when two parents share the same ancestor.
F. The Device class is copied into two separate embedded objects inside AllInOne.
Correct answers: C and D
Explanation: Diamond-shaped inheritance makes one class reachable through multiple parent paths. That means the same method name may be available from more than one place, so you must trace Python’s method resolution order to know what will actually run.
The core issue is multiple inheritance with a shared ancestor. In this example, AllInOne inherits from both Printer and Scanner, and both of those inherit from Device. That creates two paths back to the same base class, which makes method lookup harder to follow than a single straight inheritance chain.
For this hierarchy, reasoning about behavior means tracing the MRO, not just looking at one direct parent. The difficulty is not randomness; it is the extra lookup path and competing method definitions.
Device objects.Topic: Section 3: Strings
A file upload utility stores the uploaded file name in filename and the user’s note in note. Both variables are strings. The utility may continue only when .exe is absent from filename and # is absent from note. Which TWO conditions correctly use not in for these checks?
Options:
A. filename not in '.exe'
B. '#' not in note
C. note not in '#'
D. note != '#'
E. '.exe' not in filename
F. '.exe' != filename
Correct answers: B and E
Explanation: not in is the proper membership operator for checking that a character or substring does not occur anywhere in a string. The valid conditions put the forbidden text on the left and the string being searched on the right, so the result is True only when that text is absent.
In a string membership test, the left side is the character or substring you want to find, and the right side is the string you want to search. The expression 'text' not in some_string evaluates to True only when that character sequence does not appear anywhere inside some_string.
That is why '.exe' not in filename and '#' not in note are correct absence checks. Reversing the order changes the meaning, because Python would then ask whether the entire variable value appears inside the short literal. Using != is also different: it compares whole strings for equality and does not search within them.
For substring or character absence in a string, use not in with the searched text first and the target string second.
filename not in '.exe' tests whether the whole filename appears inside the literal .exe.note != '#' only asks whether the entire note is exactly #, not whether # appears inside it.'.exe' != filename compares full strings for inequality and does not perform a substring search.note not in '#' searches in the wrong direction and does not test whether # is absent from note.Topic: Section 2: Exceptions
A developer expects bad number when invalid input is processed, but that message never appears; for "x" the program prints generic failure instead.
values = ["4", "x"]
for v in values:
try:
print(12 / int(v))
except Exception:
print("generic failure")
except ValueError:
print("bad number")
What is the best fix?
Options:
A. Place except ValueError before except Exception.
B. Move int(v) outside the try block.
C. Use raise inside except Exception to let except ValueError run.
D. Replace except Exception with except BaseException.
Best answer: A
Explanation: Python checks except clauses from top to bottom. Here, int("x") raises ValueError, but except Exception catches it first because ValueError inherits from Exception. The specific handler must come before the general one.
The core rule is exception-handler ordering: Python executes the first except clause whose type matches the raised exception. In this code, int("x") raises ValueError. Because ValueError is part of the Exception hierarchy, the earlier except Exception clause already matches, so the later except ValueError clause is effectively unreachable for that error.
except Exception only as a last resort.A common mistake is assuming Python will keep checking later handlers after entering a broad one, but it does not.
except.BaseException makes the problem worse by catching even more cases before the specific handler.try block from handling the ValueError at all.Topic: Section 3: Strings
A developer defines a class so that printing an object returns the text after the last :: in an event code. What is printed by this program?
class Event:
def __init__(self, code):
self.code = code
def __str__(self):
pos = self.code.rfind("::")
return self.code[pos + 2:]
print(Event("SYS::IO::WARN"))
Options:
A. WARN
B. IO::WARN
C. SYS::IO::WARN
D. SYS::IO
Best answer: A
Explanation: rfind() searches for the last occurrence of a substring and returns its starting index. In SYS::IO::WARN, the final :: is immediately before WARN, so slicing from two characters after that index returns WARN.
The key concept is str.rfind(sub): it returns the highest index where sub begins. In the __str__() method, self.code.rfind("::") finds the second ::, not the first one. Since the separator has length 2, the slice starts at pos + 2, which skips over that final separator and keeps only the text that follows it.
SYS::IO::WARN contains :: twice.rfind("::") points to the last one.self.code[pos + 2:] returns the trailing part of the string.A common mistake is to think of rfind() as if it behaved like find(), which would lead to the substring after the first separator instead.
IO::WARN assumes the first :: was used instead of the last one.SYS::IO would come from slicing before the last separator, not after it.__str__() returns only a slice of self.code.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
A maintenance script has two separate tasks: read log.txt as UTF-8 text and count the substring ERROR, and load packet.bin as mutable raw data so one byte can be changed before saving. Which TWO approaches use the correct type for the file mode and requirement?
Options:
A. Open packet.bin with 'rt', store f.read() in buf, then assign buf[0] = '\xff'.
B. Open log.txt with 'rt', encoding='utf-8', build bytearray(f.read()), then call count('ERROR').
C. Open log.txt with 'rt', encoding='utf-8', keep f.read() as str, then call count('ERROR').
D. Open log.txt with 'rb', keep f.read() as bytes, then call count('ERROR').
E. Open packet.bin with 'rb', convert f.read() to bytearray, then assign buf[0] = 255.
F. Open packet.bin with 'rb', keep f.read() as bytes, then assign buf[0] = 255.
Correct answers: C and E
Explanation: Use str for text-mode, character-based work such as searching a UTF-8 log. Use bytearray for binary data that must be modified, because binary reads produce immutable bytes unless you convert them.
In Python, text mode ('rt') decodes file content into a str, so normal string operations such as count('ERROR') apply directly to the data. Binary mode ('rb') reads raw bytes and returns a bytes object. If you need to change individual byte values, convert that bytes object to bytearray, because bytearray is mutable while bytes is not.
Applied here, the log-processing task is text-oriented, so a str is the correct choice. The packet task is raw binary and requires mutation, so bytearray is the correct buffer type. A common near-miss is reading binary data correctly but forgetting that bytes cannot be changed in place.
bytes is not the right type for a text search using the string 'ERROR'.str is immutable.bytes is wrong because indexed assignment like buf[0] = 255 is not allowed on bytes.bytearray(f.read()) is wrong because f.read() returns a str in text mode, and that conversion is not the right fit for a text-processing task.Topic: Section 1: Modules and Packages
A developer is exploring an unfamiliar standard-library module and already has this code:
import random
rng = random.Random(0)
They want to use dir() to inspect the names exposed by the imported random module or by the existing rng object. Which two statements correctly do that? Select TWO.
Options:
A. print(random.dir())
B. print(dir("rng"))
C. print(dir(random()))
D. print(dir(rng))
E. print(rng.dir())
F. print(dir(random))
Correct answers: D and F
Explanation: dir() is a built-in function, so you use it by passing the target module or object as an argument. Passing random or rng directly is correct for inspecting exposed names in this scenario.
In Python, the correct form is dir(target), where target can be a module, class, or instance. When the target is the imported random module, dir(random) returns a list of names available from that module. When the target is the rng object, dir(rng) returns the attributes and methods visible on that instance.
A common mistake is to treat dir like a method, but it is not written as random.dir() or rng.dir(). Another mistake is inspecting the wrong thing: random() is invalid here because random refers to the module object, and "rng" is only a string literal, not the existing Random instance. The key rule is simple: pass the actual module or object directly to dir().
random.dir() fails because dir is not a module method.rng.dir() fails because instances do not use dir as an attribute-inspection method.random() fails because random in the snippet is the imported module, not a callable."rng" inspects a string object, not the existing rng variable from the code.Topic: Section 3: Strings
In Python 3, what is the output of this code?
s = "€"
try:
b = s.encode("utf-8")
if len(s) != len(b):
raise ValueError(len(s), len(b))
except ValueError as e:
print(e.args[0], e.args[1])
finally:
print(type(b).__name__)
Options:
A. 3 3, then bytes
B. 1 3, then str
C. 1 1, then bytes
D. 1 3, then bytes
Best answer: D
Explanation: len(s) counts characters in the Unicode string, while len(s.encode("utf-8")) counts UTF-8 bytes. For €, those values are 1 and 3, so the ValueError handler prints 1 3, and finally prints bytes.
A Python str stores Unicode text, not raw UTF-8 bytes. UTF-8 is a variable-length encoding, so one Unicode character may take more than one byte when encoded.
s contains one character: €b = s.encode("utf-8") creates a bytes object€ uses 3 bytes in UTF-8, so len(s) is 1 and len(b) is 3ValueError(1, 3) is raised and caught1 3finally block always runs and prints bytesThe closest mistake is assuming one character always equals one byte, which is not true for many non-ASCII Unicode characters.
€ is one character but not one UTF-8 byte.bytes object has length 3; the original str still has length 1.encode() returns bytes, not str.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
A developer needs to read the first line from an existing text file and append its uppercase version to the end of the same file.
data.txt initially contains:
one
two
The current code does not produce the required final contents.
with open("data.txt", "a") as f:
first = f.readline().strip()
f.write(first.upper() + "\n")
The required final contents are:
one
two
ONE
Which replacement is the best fix?
Options:
A. Open with r+ and keep the rest of the code unchanged.
B. Open with w+ and keep the rest of the code unchanged.
C. Open with a+ and add f.seek(0) before readline().
D. Open with a+ and keep the rest of the code unchanged.
Best answer: C
Explanation: The code must both read existing content and append new content without deleting the file. a+ allows both operations, but it opens the file at the end, so f.seek(0) is required before reading the first line.
This is a file-mode and cursor-position problem. The file must stay intact, the first line must be read, and the uppercase version must be added at the end. a+ is the right mode because it allows reading and writing without truncating the file, and writes in append mode go to the end of the file.
a+.f.seek(0) to move from EOF to the start.readline().The closest wrong choice is r+: it allows reading and writing, but after reading the first line the next write occurs at the current position, so it overwrites existing content instead of appending.
r+ is tempting because it supports reading and writing, but the write starts at the current cursor position rather than appending.w+ fails because opening the file in that mode truncates it immediately.a+ without seek(0) starts reading at EOF, so readline() returns an empty string.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)
Options:
A. It raises TypeError on c.add(5)
B. It prints 15
C. It prints 10
D. It raises NameError on self.value += step
Best answer: A
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.
15 assumes a correct instance-method signature; the missing self prevents the method call from succeeding.10 is wrong because the program stops at c.add(5) and never reaches print(c.value).NameError overlooks that Python detects the wrong argument count first, so the method body is not executed.Topic: Section 1: Modules and Packages
A developer wants this snippet to print 6.0, but it raises an error:
from math import hypot, sqrt
result = math.hypot(6, 8) - sqrt(16)
print(result)
Which change is the best fix?
Options:
A. Change only the first line to import math
B. Change the calculation to result = hypot([6, 8]) - sqrt(16)
C. Change the calculation to result = math.hypot(6, 8) - math.sqrt(16)
D. Change the calculation to result = hypot(6, 8) - sqrt(16)
Best answer: D
Explanation: from math import hypot, sqrt imports those function names directly into the current namespace. In this snippet, sqrt(16) is already correct, but math.hypot(6, 8) is not because no math module name was imported.
Python supports two common import styles for module functions. With import math, you call functions through the module name, such as math.sqrt(16). With from math import hypot, sqrt, you call the imported names directly, such as hypot(6, 8) and sqrt(16).
Here, the code uses the second style, so only the hypot call is wrong. After the fix, the expression evaluates normally: hypot(6, 8) returns 10.0, sqrt(16) returns 4.0, and the final result is 6.0.
The key takeaway is to match the function call style to the way the module was imported.
import math leaves sqrt(16) undefined, because sqrt is no longer imported directly.math. still fails, because the name math does not exist after from math import hypot, sqrt.[6, 8] to hypot uses the wrong signature; hypot expects numeric arguments, not a single list.Topic: Section 1: Modules and Packages
A team stores constants and a helper class in settings.py:
# settings.py
timeout = 30
_retry_limit = 5
__trace = True
class Worker:
__mode = "safe"
Another file executes:
from settings import *
import settings
Which statement about the module variables is correct?
Options:
A. __trace is name-mangled to _settings__trace because double underscores always mangle names.
B. Only _retry_limit is skipped by wildcard import; __trace is imported because it uses two underscores.
C. timeout is public; _retry_limit and __trace are non-public by convention and are skipped by wildcard import.
D. _retry_limit becomes a class variable of Worker because it is defined before the class.
Best answer: C
Explanation: At module level, leading underscores mark non-public names by convention. That means timeout is public, while _retry_limit and __trace are skipped by from settings import *; double underscores here do not create class-style name mangling.
Python modules use naming conventions, not true access control, to signal public versus non-public names. A module variable with no leading underscore, such as timeout, is public. Any module name beginning with _, including _retry_limit and __trace, is considered non-public by convention.
This convention also affects from module import *: wildcard import omits names that start with an underscore unless the module explicitly defines __all__. The key trap is confusing module names with class attributes. Double leading underscores trigger name mangling only inside class definitions, such as Worker.__mode, not for module-level variables like __trace.
So __trace remains a normal name in the module namespace, but wildcard import still skips it because it starts with _.
__trace is not name-mangled; name mangling applies to class attributes._, including names starting with __.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
Assume data.txt already exists, so open("data.txt", "r") succeeds. A developer wants to write text but opened the file in read mode. Which statement about this code is correct?
from io import UnsupportedOperation
try:
fh = open("data.txt", "r")
fh.write("X")
except UnsupportedOperation as ex:
print("mode problem:", ex.args[0])
except (OSError, ValueError):
print("fallback")
else:
print("write ok")
finally:
print("finished")
fh.close()
Options:
A. The UnsupportedOperation handler runs, else is skipped, and finally still runs.
B. else runs because opening the file succeeded before the write() call.
C. No exception is raised because 'r' mode allows both reading and writing.
D. The grouped (OSError, ValueError) handler runs first because UnsupportedOperation is also an OSError.
Best answer: A
Explanation: This is a file mode mismatch: write() is attempted on a handle opened with 'r', so Python raises io.UnsupportedOperation. The specific except UnsupportedOperation as ex block handles it first, else does not run, and finally runs anyway.
File modes control which operations are allowed on a handle. Mode 'r' opens the file for reading only, so calling write() on that handle raises io.UnsupportedOperation. In this code, exception matching is checked top to bottom, and the specific except UnsupportedOperation as ex block is reached before the broader grouped handler.
A quick way to reason about the flow is:
open("data.txt", "r") succeeds.fh.write("X") fails because the handle is not writable.except runs.else is skipped because an exception occurred.finally runs whether or not the exception was handled.The closest distractor is the grouped handler, but broader handlers do not run when an earlier, more specific handler already matched.
except blocks are checked in order, so the specific UnsupportedOperation block catches the error before the grouped one.else after open fails because else runs only when the entire try block completes without any exception.'r' is read-only; writing requires a writable mode such as 'w', 'a', or 'r+'.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
A developer is cleaning usernames before saving a report. The script should remove surrounding spaces and convert each name to uppercase, but instead of ['ALICE', 'BOB', 'CAROL'] it prints a list of built-in method objects.
names = [" alice ", " Bob ", " carol "]
cleaned = list(map(lambda s: s.strip().upper, names))
print(cleaned)
What is the best fix?
Options:
A. Call upper() inside the lambda.
B. Convert names to a tuple first.
C. Replace map() with filter().
D. Remove list() from the assignment.
Best answer: A
Explanation: map() is fine here; the problem is the lambda body. s.strip().upper refers to the string method object, while s.strip().upper() actually calls the method and returns the uppercase result.
map() applies the lambda to each item in names, so the key question is what the lambda returns. In this code, s.strip().upper first creates the stripped string, then accesses its upper method without calling it. That means each mapped result is a method object, not transformed text.
The fix is to call the method:
cleaned = list(map(lambda s: s.strip().upper(), names))
Now each element is stripped and converted to uppercase before being collected into a list. map() is the right tool because every input element must be transformed, not filtered out or left as-is.
map() with filter() fails because filter() keeps or discards items; it does not transform each string.names to a tuple does not change what the lambda returns.list() would only leave a map iterator; the lambda would still produce method objects.Topic: Section 3: Strings
A developer scans a short status tag one character at a time and builds a new string. What is printed by this code?
tag = "Py 3!"
result = ""
for ch in tag:
if ch.isalpha():
result += ch.lower()
elif ch == " ":
result += "-"
else:
result += "*"
print(result)
Options:
A. py-**
B. py**
C. py-3!
D. Py-**
Best answer: A
Explanation: The for ch in tag loop iterates through the string one character at a time, including the space, digit, and punctuation. Letters are lowercased, the space becomes -, and all other characters become *, so the final result is py-**.
A Python string is iterable, so for ch in tag visits each character in order: P, y, space, 3, and !. For alphabetic characters, ch.isalpha() is True, so ch.lower() adds p and y to result. When the loop reaches the space, the second branch matches and adds -. The remaining characters, 3 and !, are neither alphabetic nor spaces, so they both go to the else branch and become *. After all characters are processed, print(result) outputs py-**.
The key takeaway is that string iteration handles every character individually unless the code explicitly skips one.
P ignores that alphabetic characters are passed through .lower().- treats the space as if it were skipped, but the code explicitly replaces it.3! ignores that nonletters that are not spaces fall into the else branch.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?
Options:
A. class AmphibiousVehicle(Vehicle): pass
B. class AmphibiousVehicle(LandVehicle): pass
C. class AmphibiousVehicle(WaterVehicle): pass
D. class AmphibiousVehicle(LandVehicle, WaterVehicle): pass
Best answer: D
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.
Vehicle skips the requirement to derive from both specialized classes.LandVehicle is still single inheritance, so the water-side parent is missing.WaterVehicle has the same problem in reverse.Topic: Section 3: Strings
A utility receives import targets such as pkg.tools.reader and reader. It must locate the last . so it can separate the package path from the final module name, and it must return -1 when no dot exists.
name = target
cut = ???
Which replacement should be used?
Options:
A. name.index(".")
B. name.rfind(".")
C. name.find(".")
D. name.rindex(".")
Best answer: B
Explanation: Use rfind() when you need the last occurrence of a substring and a non-exception result for “not found.” That matches import targets that may be fully qualified like pkg.tools.reader or standalone like reader.
The key concept is that .rfind() searches for the last occurrence of a substring and returns its index, or -1 if the substring is absent. In a module/package scenario, the last dot separates the package path from the final module name, so searching from the right is the correct behavior.
pkg.tools.reader.rfind(.) points to the dot before reader.reader.rfind(.) returns -1.That makes .rfind() a safe choice when some import targets are not package-qualified. The closest distractor is .rindex(), which also finds the last occurrence but raises ValueError when the dot is missing.
find() fails because it returns the first dot, not the last one.index() fails because it searches from the left and raises ValueError if no dot exists.rindex() finds the last dot, but it still raises ValueError when the string has no dot.Topic: Section 4: Object-Oriented Programming
A developer placed this class in a package module and imports it elsewhere.
# app/models.py
class User:
def __init__(self, name, level):
name = name
level = level
# main.py
from app.models import User
u = User("Ana", 3)
print(hasattr(u, "name"), hasattr(u, "level"))
Which replacement for __init__() correctly binds both constructor arguments to instance variables?
Options:
A. def __init__(self, name, level): self.name = name; self.level = level
B. def __init__(name, level): self.name = name; self.level = level
C. def __init__(self, name, level): User.name = name; User.level = level
D. def __init__(self, name, level): name = self.name; level = self.level
Best answer: A
Explanation: Constructor parameters are local variables until they are attached to the object. Using self.name = name and self.level = level stores the values on each User instance.
In Python, __init__() receives the new object as self. Parameters such as name and level exist only inside that method unless you assign them to attributes on self. That is why self.name = name and self.level = level are the correct bindings for instance variables.
The import statement in main.py is just scenario context; it does not change how object initialization works. The original code uses name = name and level = level, which only refers to local names and creates no attributes on the object. Assigning through User.name would create class attributes shared by instances, not per-instance state. The key idea is that instance data must be stored through self.
self as the first parameter is invalid for normal instance initialization.User.name and User.level creates class variables, not instance variables.self.name and self.level before they exist.Topic: Section 4: Object-Oriented Programming
A developer wants print(t) to display Ticket 17 for Ana, but this code prints the default object representation instead.
class Ticket:
def __init__(self, number, owner):
self.number = number
self.owner = owner
def __str_(self):
return f"Ticket {self.number} for {self.owner}"
t = Ticket(17, "Ana")
print(t)
Which change is the best fix?
Options:
A. Change the return line to use Ticket.number and Ticket.owner.
B. Change the method to def __str__(self, extra):.
C. Replace print(t) with print(t.number, t.owner).
D. Rename __str_ to __str__ and keep the current body.
Best answer: D
Explanation: print(obj) internally uses str(obj), which looks for a method named exactly __str__. Because the class defines __str_ instead, Python falls back to the default object text inherited from object.
This is a special-method lookup problem. In Python, print(t) calls str(t), and str(t) looks for a method named exactly __str__ with two trailing underscores. A misspelled name like __str_ is treated as an ordinary method, so it is ignored during string conversion.
The fix is to rename the method header to the exact special name:
def __str__(self):After that, print(t) will use the returned text, such as Ticket 17 for Ana. Changing the print() call may display some data, but it does not correct the object’s string representation behavior.
__str__ must be callable for the instance without an added argument.number and owner were stored on each instance, not on the Ticket class.print() bypasses the real issue and does not define how the object should convert to a string.Topic: Section 4: Object-Oriented Programming
A developer is checking which members belong to the class itself in a small reporting app.
class Report:
category = "daily"
__limit = 3
def __init__(self, name):
self.name = name
def show(self):
return f"{self.name}:{Report.category}"
r = Report("sales")
Which statement about Report.__dict__ is correct?
Options:
A. It contains name because self.name is assigned in __init__.
B. It stores the private attribute under the key __limit.
C. It contains category, _Report__limit, __init__, and show.
D. The value of Report.__dict__['show'] is the bound method from r.show.
Best answer: C
Explanation: Report.__dict__ represents the class namespace, so it includes class-level attributes and methods defined in the class body. The private attribute is name-mangled to _Report__limit, while name belongs to the instance r, not to the class.
A class __dict__ shows names stored on the class itself. In this example, category is a class variable, and __init__ plus show are entries created by method definitions inside the class body. The attribute __limit starts with two underscores, so Python applies name mangling and stores it under _Report__limit in Report.__dict__.
By contrast, self.name = name creates an instance attribute during object initialization. That means name is found in r.__dict__, not in Report.__dict__. Also, the class dictionary stores the function object for show; it becomes a bound method only when accessed through an instance such as r.show.
So, when inspecting a class __dict__, look for class variables, methods, and mangled private names, not per-instance data.
name appears in Report.__dict__ confuses an instance attribute with a class attribute.__limit unchanged ignores Python name mangling for double-underscore class names.show as already bound misses that binding happens when the function is accessed through r.Topic: Section 4: Object-Oriented Programming
Given these files, what is printed when main.py is run?
# tools.py
class Hammer:
pass
# main.py
from tools import Hammer
class Nail:
pass
print(Hammer.__module__)
print(Nail.__module__)
Options:
A. tools, then main
B. tools, then __main__
C. __main__, then __main__
D. Hammer, then Nail
Best answer: B
Explanation: A class’s __module__ attribute stores the name of the module where that class was defined. Importing a class into another file does not change that value, and a file run directly uses the module name __main__.
The key concept is that ClassName.__module__ identifies the defining module, not the file currently using the class. Hammer is defined in tools.py, so Hammer.__module__ is tools even after from tools import Hammer. Nail is defined inside main.py, and because main.py is executed directly as the script, that module’s runtime name is __main__.
So the output is:
tools
__main__
The closest mistake is using main for Nail, but that would apply only if the file were imported as a module rather than run as the main script.
tools, then main misses that a directly executed script has module name __main__.__main__ for both lines incorrectly assumes importing a class changes its defining module.Hammer, then Nail confuses __module__ with class names such as __name__.Topic: Section 4: Object-Oriented Programming
A developer is checking method lookup in a diamond-shaped inheritance hierarchy. What is printed by this Python 3 code?
class Top:
def name(self):
return "Top"
class Left(Top):
def show(self):
return self.name()
class Right(Top):
def name(self):
return "Right"
class Bottom(Left, Right):
pass
print(Bottom().show())
Options:
A. Left
B. AttributeError is raised
C. Top
D. Right
Best answer: D
Explanation: The output is Right. Python uses method lookup based on the instance’s method resolution order (MRO), not only the class where the current method was found. After show() is taken from Left, self.name() is searched on the Bottom instance through Left, Right, and then Top.
Python resolves methods in multiple inheritance by following the method resolution order (MRO). For Bottom, that order is Bottom, Left, Right, Top, then object. The call to show() finds Left.show, but inside that method, self still refers to the Bottom instance. So self.name() is looked up using Bottom’s MRO as well. Left does not define name(), so Python continues to Right, finds Right.name(), and returns Right.
The key takeaway is that method lookup continues from the instance’s class hierarchy, not from only the class where the current method was found.
Top ignores that Right.name() appears earlier than Top.name() in Bottom’s lookup path.Left confuses the class that provides show() with the class that provides name().name() method does exist in the inheritance hierarchy, so lookup succeeds.Topic: Section 4: Object-Oriented Programming
A teammate is filling in # TODO in the class below. The method must be an instance method, follow the usual self convention, and let the else clause run.
class User:
def __init__(self, name):
self.name = name
# TODO
u = User("Ana")
try:
print(u.label())
except TypeError as e:
print(type(e).__name__, e.args[0])
else:
print("ok")
finally:
print("done")
Which replacement is best?
Options:
A. def label(self, value): return self.name
B. def label(): return self.name
C. def label(self): return self.name
D. def label(obj): return obj.name
Best answer: C
Explanation: Instance methods receive the instance automatically when called through an object, so the method must accept that first argument. Using self as that first parameter is the normal convention, and this version allows u.label() to succeed, so else executes.
When Python evaluates u.label(), it creates a bound method call and passes u as the first argument automatically. For a normal instance method, the first parameter should therefore represent the instance, and by convention that parameter is named self.
If the method has no first parameter, the call gets one argument too many and raises TypeError. If it requires an extra parameter, calling u.label() without that value also raises TypeError. A name like obj would technically work, but it does not match the stated requirement to use the usual self convention.
So the best replacement is the one that both works at runtime and declares the instance parameter as self.
u.label() still passes the instance, causing a TypeError.obj would run, but it does not satisfy the requested self naming convention.value fails because the call provides only the bound instance, not a second argument.Topic: Section 3: Strings
A developer needs to send Python text through a byte-only channel and later rebuild the same value. The text may contain non-ASCII characters.
text = "Δx = café"
packet = text.encode("ascii")
restored = packet.decode("utf-8")
print(restored == text)
The program currently fails. Which change is the best fix?
Options:
A. Replace text.encode("ascii") with text.decode("utf-8").
B. Replace restored = packet.decode("utf-8") with restored = str(packet).
C. Replace text.encode("ascii") with ascii(text).encode("utf-8").
D. Replace text.encode("ascii") with text.encode("utf-8").
Best answer: D
Explanation: In Python, str values are Unicode text, not raw bytes. ASCII cannot represent characters like Δ or é, but UTF-8 can, so using UTF-8 for both encoding and decoding correctly round-trips the string.
The core concept is the difference between text and encodings. In Python 3, text is a Unicode str, so it already represents characters such as Δ and é. ASCII is a limited encoding that supports only 128 basic characters, so trying to encode this string as ASCII raises UnicodeEncodeError.
UTF-8 is a byte encoding for Unicode text. The correct flow is:
strencode("utf-8")decode("utf-8")That preserves the original content exactly. By contrast, ascii() creates an escaped representation, and str(packet) creates a printable bytes representation, not the decoded text.
ascii(text) fails because it turns non-ASCII characters into escape sequences, changing the original content.decode() on text fails because decode() is for bytes, while text is already a Unicode str.packet with str(packet) fails because it produces a representation like b'...', not the recovered text.Topic: Section 1: Modules and Packages
A developer replaces import helpers with from helpers import * in main.py.
Assume helpers.py is on sys.path and does not define __all__.
# helpers.py
value = 10
_hidden = 99
def show():
return value
Which statement is correct after from helpers import * runs in main.py?
Options:
A. In main.py, value and show become local names, increasing collision risk; _hidden and helpers do not.
B. In main.py, public names are imported only when first used.
C. In main.py, value, _hidden, show, and helpers all become local names.
D. In main.py, only helpers becomes a local name, so collisions are avoided.
Best answer: A
Explanation: A star import adds public names from the module directly to the current namespace instead of creating a module name like helpers. Without __all__, names starting with _ are excluded, so this form is convenient but increases namespace pollution and naming-collision risk.
The key concept is that from module import * changes the caller’s namespace directly. In main.py, the public variable value and the function show become ordinary names available without qualification. That is the main risk: they can overwrite existing names in main.py, or later assignments in main.py can hide them.
Because helpers.py does not define __all__, Python uses the default rule for star import and skips names that start with _, so _hidden is not imported. Also, this import form does not create the name helpers; that happens with import helpers, not with from helpers import *.
A close distractor confuses star import with regular module import, but those two forms affect the namespace very differently.
helpers is imported confuses from helpers import * with import helpers._hidden is also imported ignores the default rule that skips leading-underscore names when __all__ is absent.Topic: Section 2: Exceptions
An engineer uses a small trace function to verify cleanup behavior. What is printed by this code?
def trace(n):
try:
print("T", end="")
10 // n
except ZeroDivisionError:
print("E", end="")
else:
print("L", end="")
finally:
print("F", end="")
for value in [2, 0]:
trace(value)
print("!", end="")
Options:
A. TEFTLF!
B. TLFTEF!
C. TLTEFF!
D. TLFTE!
Best answer: B
Explanation: finally runs after the try block whether an exception happens or not. For 2, the code prints T, then L, then F; for 0, it prints T, then E, then F, followed by !.
The key concept is that a finally clause always executes after the try statement finishes its current path. If no exception is raised, control goes from try to else to finally. If a matching exception is raised, control goes from try to except to finally.
2, 10 // 2 succeeds, so the trace is T, L, F.0, 10 // 0 raises ZeroDivisionError, so the trace is T, E, F.print adds !.That produces TLFTEF!. The closest mistake is skipping the second F, but finally still runs after the exception is handled.
F fails because finally still runs after ZeroDivisionError is caught.TEF fails because the loop processes 2 before 0.FF together fails because finally runs once per function call, immediately after else or except.Topic: Section 2: Exceptions
Review this code in a single module:
class AppError(Exception):
pass
class ConfigError(AppError):
pass
def parse_timeout(text):
value = int(text)
if value < 0:
raise ConfigError("negative timeout")
return value
try:
print(parse_timeout("abc"))
except Exception as err:
print("Settings error:", err)
The handler should catch only application-defined exceptions derived from AppError and let unrelated built-in exceptions such as ValueError propagate. What is the best fix?
Options:
A. Replace the handler with except ConfigError as err:
B. Add except AppError as err: before the existing except Exception as err:
C. Replace the handler with except AppError as err:
D. Replace the handler with except (AppError, Exception) as err:
Best answer: C
Explanation: Use the shared custom base class in the except clause. AppError catches ConfigError and any future app-defined subclasses, while built-in exceptions such as ValueError are no longer swallowed by a broad except Exception.
When several application-specific exceptions belong to the same family, the safest pattern is to derive them from a custom base class and catch that base class. Here, ConfigError inherits from AppError, so except AppError will handle it and any future subclasses in the same hierarchy.
Because ValueError comes from the built-in Exception branch rather than from AppError, it will bypass that handler and propagate normally. That is exactly what the requirement asks for. Leaving except Exception in place, or including Exception in a tuple, is still too broad because it matches unrelated errors raised inside the try block.
The key takeaway is to catch the narrowest custom base class that represents the exceptions you actually want to handle.
ConfigError is too narrow because the requirement includes future exceptions derived from AppError.except Exception still leaves unrelated built-in exceptions caught by the generic block.Exception remains too broad, so built-in errors like ValueError are still caught accidentally.Topic: Section 3: Strings
A developer wants to copy each non-space character from a label and add - after it. What is printed by this Python 3 code?
label = "PC AP"
out = ""
for ch in label:
if ch == " ":
continue
out += ch + "-"
print(out)
Options:
A. P-C-A-P-
B. P-C-A-P
C. PC-AP-
D. P-C- -A-P-
Best answer: A
Explanation: for ch in label iterates through the string one character at a time. The space is reached as its own character, but continue skips it, so only P, C, A, and P are added, each followed by -.
Strings are iterable, so for ch in label: reads characters from left to right, one at a time. For "PC AP", the loop sees P, C, a space, A, and P. When ch is a space, continue immediately starts the next iteration, so nothing is appended for that character. For every other character, out += ch + "-" adds the character and then a hyphen.
P, out is P-C, out is P-C-out stays P-C-A and P, out becomes P-C-A-P-The key takeaway is that direct string iteration works character by character, including spaces unless your code skips them.
PC-AP- treats the string like two chunks, but direct iteration returns single characters.continue skips the append step when ch is ' '.ch + "-" runs for every non-space character, including the last P.Topic: Section 2: Exceptions
A developer is debugging a calculation and wants one except block to print the caught exception’s class name and message. What is printed?
def run(value):
try:
print(10 / int(value))
except (ValueError, ZeroDivisionError) as err:
print(type(err).__name__, err.args[0])
finally:
print("done")
run("0")
Options:
A. ZeroDivisionError division by zero
B. ValueError invalid literal for int() with base 10: ‘0’ done
C. ZeroDivisionError (‘division by zero’,) done
D. ZeroDivisionError division by zero done
Best answer: D
Explanation: The grouped handler catches the ZeroDivisionError raised by 10 / 0. Because as err binds the exception object to a local name, the code can read both type(err).__name__ and err.args[0], and finally still prints done.
except ... as name assigns the caught exception instance to a local name inside that handler. In this code, int("0") succeeds and returns 0, but 10 / 0 raises ZeroDivisionError. That exception matches the grouped handler and is bound to err.
type(err).__name__ returns ZeroDivisionErrorerr.args is a tuple, so err.args[0] is division by zerofinally runs whether or not an exception was raisedSo the program prints the exception type and message, then prints done. The closest wrong choice confuses the whole args tuple with its first element.
ValueError fails because int("0") does not raise an exception.('division by zero',) uses err.args, but the code prints err.args[0].finally always runs after the handled exception.Topic: Section 4: Object-Oriented Programming
A developer expects this program to print ok, but it prints bad output instead.
class Ticket:
def __init__(self, code):
self.code = code
def _str_(self):
return self.code
t = Ticket("R-5")
try:
assert str(t) == "R-5"
except AssertionError as err:
print("bad output")
else:
print("ok")
finally:
print("done")
Which change fixes the problem so the else block runs?
Options:
A. Move print("ok") into finally
B. Make _str_ return str(self.code)
C. Catch Exception instead of AssertionError
D. Rename _str_ to __str__
Best answer: D
Explanation: The method is misspelled. str(t) looks specifically for __str__, so Python ignores _str_, the assertion fails, and the except block runs instead of else.
str(obj) uses a special method named exactly __str__(). In this class, the method is written as _str_, which is just a normal method and is not used by str(t). Because of that, str(t) does not return "R-5", so the assert statement raises AssertionError.
The exception flow is then:
try: evaluates the failed assertionexcept AssertionError as err: catches the failure and prints bad outputelse: skipped because an exception occurredfinally: always runs and prints doneRenaming _str_ to __str__ fixes the object’s string conversion behavior; changing handlers or finally does not fix the underlying class problem.
_str_ return str(self.code) still leaves the special method misspelled, so str(t) never calls it.Exception changes the handler, but the assertion still fails and the else block still does not run.print("ok") into finally changes output placement, not the broken string-conversion behavior.Topic: Section 4: Object-Oriented Programming
A developer wants one Wallet object to preserve its balance across several method calls. The code should print 12, but it fails:
class Wallet:
def __init__(self, amount=0):
self.amount = amount
def add(self, value):
amount += value
def spend(self, value):
self.amount -= value
w = Wallet(10)
w.add(5)
w.spend(3)
print(w.amount)
Which change is the best fix?
Options:
A. Replace amount += value with Wallet.amount += value.
B. Replace amount += value with self.amount += value.
C. Change def add(self, value): to def add(value, self):.
D. Replace amount += value with amount = self.amount + value.
Best answer: B
Explanation: Object state for one instance is stored in its instance attributes, accessed through self. Changing add to use self.amount += value lets the same Wallet object move from 10 to 15 to 12 across the two method calls.
In Python, an object’s changing state is usually kept in instance attributes such as self.amount. Inside add, the bare name amount is not the wallet’s stored balance; it is treated as a local variable name, so amount += value does not update the instance.
After replacing that line with self.amount += value, the state changes happen on the same object:
Wallet(10) sets w.amount to 10w.add(5) changes w.amount to 15w.spend(3) changes w.amount to 12The key point is to update the instance attribute, not a local name or a class attribute.
Wallet.amount targets a class attribute, not this object’s stored balance.amount = self.amount + value only creates a local variable unless the result is assigned back to self.amount.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
A developer is inspecting a package initializer. The text file pkg/__init__.py contains exactly these three lines, and each line ends with \n:
from .tools import run
__all__ = ["run"]
_hidden = 0
What value is assigned to rest after this code runs?
with open("pkg/__init__.py", "r") as f:
f.readline()
rest = f.readlines()
Options:
A. ['__all__ = ["run"]\n', '_hidden = 0\n']
B. ['__all__ = ["run"]', '_hidden = 0']
C. ['from .tools import run\n', '__all__ = ["run"]\n', '_hidden = 0\n']
D. '__all__ = ["run"]\n_hidden = 0\n'
Best answer: A
Explanation: readlines() reads from the current file position to the end of the file and returns a list of the remaining lines. Because readline() already consumed the first line, only the last two lines appear in rest, and their \n characters remain.
The key concept is that file reading methods move the file cursor. After f.readline(), the first line has already been read, so the cursor is positioned at the start of the second line. Calling f.readlines() then reads all remaining lines up to end-of-file and returns them as a list of strings.
Because the file is opened in text mode and the stem states that each line ends with \n, each returned list element keeps its trailing newline. That means rest contains the second and third lines only: __all__ = ["run"]\n and _hidden = 0\n.
The closest mistake is assuming readlines() starts again from the top of the file; it does not.
readline().readlines() with read(), which returns one string.\n assumes line endings are stripped automatically, but readlines() preserves them here.Topic: Section 4: Object-Oriented Programming
A developer is testing a class method that updates object state and may raise an exception. What is the output of this program?
class LimitError(Exception):
pass
class Counter:
def __init__(self):
self.value = 1
def bump(self, step):
self.value += step
if self.value > 3:
raise LimitError(self.value)
return self.value
c = Counter()
try:
print(c.bump(1))
print(c.bump(2))
except LimitError as e:
c.value -= 1
print(e.args[0], c.value)
finally:
print(c.value)
Options:
A. 2; 4 4; 4
B. 2; 3 3; 3
C. 2; 4; 3
D. 2; 4 3; 3
Best answer: D
Explanation: The second call to bump() changes the object’s state before the exception is raised. That means the exception carries 4, the except block changes the stored value to 3, and finally prints 3.
bump() modifies the instance attribute through self, and that change is not undone when the exception is raised. On the first call, value goes from 1 to 2, so 2 is printed. On the second call, value goes from 2 to 4, then LimitError(4) is raised before the outer print() can print a return value.
e.args[0] is 4 because that was passed to the exception.c.value -= 1, so the object’s state becomes 3.finally block always runs and prints the current value, which is 3.The key point is that the method updates object state first, and the exception handler updates it again afterward.
3 3 treats the exception argument as if it changed after the handler modified the object.4 4 ignores the c.value -= 1 statement inside the except block.4 before the last line misses that print(e.args[0], c.value) outputs two values on one line.Topic: Section 2: Exceptions
A developer expects invalid age data to be handled, but this program stops with an uncaught exception when run as shown.
def load_age(record):
try:
age = int(record["age"])
print(100 / age)
except KeyError:
print("missing age")
except ZeroDivisionError:
print("age cannot be zero")
load_age({"age": "abc"})
What is the best explanation?
Options:
A. ZeroDivisionError occurs first, so the handler order is wrong.
B. The try block protects only the division line.
C. int("abc") raises ValueError, and no handler catches it.
D. record["age"] raises KeyError because the value is invalid.
Best answer: C
Explanation: The try block covers both statements, but only KeyError and ZeroDivisionError are handled. Because int(record["age"]) raises ValueError for 'abc', that exception does not match any except clause and propagates out of the function.
An exception is handled only when its type matches one of the except clauses attached to the current try block. In this code, the try block includes both the conversion and the division. With {"age": "abc"}, the dictionary lookup succeeds because the key exists, but int("abc") raises ValueError.
KeyError would be caught only if "age" were missing.ZeroDivisionError would be caught only if conversion succeeded and the value became 0.Since no except ValueError clause exists, the exception is not handled here and continues upward. The closest wrong idea is confusing an invalid value with a missing key.
"age" key exists; the problem is converting its value.try suite is protected.Topic: Section 1: Modules and Packages
A developer has two package roots on the same machine:
/proj/lib/tools/__init__.py
/proj/lib/tools/helper.py # version = "new"
/legacy/tools/__init__.py
/legacy/tools/helper.py # version = "old"
This script should import the newer package, but it prints old:
import sys
sys.path = ["/legacy", "/proj/lib"] + sys.path
from tools import helper
print(helper.version)
Which change best fixes the import behavior while keeping both package roots available?
Options:
A. Replace from tools import helper with import helper.
B. Delete the package __pycache__ directories.
C. Put "/proj/lib" before "/legacy" in sys.path.
D. Add "/proj/lib/tools" to sys.path.
Best answer: C
Explanation: Python resolves imports by scanning sys.path in order for the top-level package name. Because "/legacy" is listed first, its tools package is imported; placing "/proj/lib" first makes the intended package load instead.
For from tools import helper, Python first looks for a top-level package named tools by checking each directory in sys.path from left to right. Here, both "/legacy" and "/proj/lib" contain a valid tools package, so the first match is used. Since "/legacy" comes first, Python imports legacy/tools/helper.py and prints old.
A sys.path entry must be the parent directory of the package, not the package directory itself. Reordering the two roots fixes the issue while keeping both available. __pycache__ only stores bytecode caches, and changing the import to import helper would ask for a different top-level module.
"/proj/lib/tools" uses the package directory instead of its parent, so from tools import helper still would not locate tools correctly.__pycache__ may force recompilation, but it does not change which matching package is found first.import helper looks for a top-level module named helper, not the helper submodule inside tools.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
In Python 3, what is the exact output of this code?
def report(func, values):
print(func.__name__, [func(v) for v in values])
def cube(x):
return x ** 3
nums = [1, 2]
report(cube, nums)
report(lambda x: x + 1, nums)
Options:
A. cube [1, 8] cube [2, 3]
B. cube [1, 8]
C. cube [1, 8] lambda [2, 3]
D.
Best answer: B
Explanation: cube is created with def, so its function name is cube. The second call passes a lambda expression directly, and lambda-created function objects report their name as <lambda>. Applying them to [1, 2] produces [1, 8] and [2, 3].
A function defined with def gets the name you assign in the definition, so func.__name__ is cube in the first call. The list comprehension then applies cube to each number, giving [1, 8].
In the second call, report receives an inline lambda x: x + 1. A lambda creates an anonymous function object, so its displayed name is <lambda>. Applying that function to 1 and 2 gives [2, 3].
This reflects the usual distinction: def is preferred for named, reusable functions, while lambda is commonly used for short one-off callables passed directly where needed.
lambda without angle brackets fails because Python reports a lambda function name as <lambda>.<lambda> on both lines fails because the first call uses the named function cube.cube on both lines fails because the second call passes a different inline function, not cube.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
A developer writes a helper function that accepts a lambda and calls it for each value:
def collect(values, test):
result = []
for v in values:
if test(v):
result.append(v * 10)
return result
print(collect([1, 2, 3, 4], lambda x: x % 2 == 0))
What is printed?
Options:
A. [10, 20, 30, 40]
B. [False, True, False, True]
C. [2, 4]
D. [20, 40]
Best answer: D
Explanation: A lambda can be passed to a user-defined function just like any other callable. Here, collect() uses the lambda as a test, so only even numbers pass, and the function stores v * 10 for those values.
The core idea is that a lambda is just a function object, so it can be passed as an argument and called inside another function. In this snippet, collect() receives the lambda as test and evaluates test(v) for each item in [1, 2, 3, 4].
1, the lambda returns False2, it returns True, so 20 is appended3, it returns False4, it returns True, so 40 is appendedThe lambda controls which values are accepted, but the appended values come from v * 10. That is why the output is a list of scaled even numbers, not the original even numbers or the boolean test results.
[2, 4] misses that the function appends v * 10, not v.if test(v) condition.result.Topic: Section 5: Miscellaneous (List Comprehensions, Lambdas, Closures, I/O)
Assume open() succeeds. In this binary I/O example, a developer mixes a text string with a byte-oriented buffer.
class BinaryDataError(Exception):
pass
buf = bytearray(b"AB")
try:
try:
with open("out.bin", "wb") as f:
f.write(buf)
f.write("C")
except TypeError as ex:
raise BinaryDataError("encode text before binary write")
else:
print("saved")
except BinaryDataError as ex:
print(ex.args[0])
finally:
print(buf.decode())
What is printed?
Options:
A. saved then AB
B. TypeError then AB
C. encode text before binary write then ABC
D. encode text before binary write then AB
Best answer: D
Explanation: Binary file writes accept bytes-like objects such as bytearray, not str. The first write succeeds, the second raises TypeError, that error is replaced by a custom exception, and finally still runs.
Binary I/O in Python works with bytes-like data, not text strings. Here, buf is a bytearray, so f.write(buf) is valid in 'wb' mode. But f.write("C") sends a str to a binary stream, which raises TypeError. The inner except catches that and raises BinaryDataError("encode text before binary write"), so the inner else block is skipped. The outer except prints the custom exception message, and the finally block runs regardless of success or failure. Since the file write does not change buf, buf.decode() still prints AB.
The closest wrong idea is the output with saved, but else runs only when no exception occurs in the inner try.
saved fails because the second write raises an exception, so the inner else block does not run.TypeError fails because that exception is caught and replaced with BinaryDataError.ABC fails because writing to the file does not modify the original bytearray.Topic: Section 4: Object-Oriented Programming
What is printed by this code?
class LimitError(Exception):
pass
class Tracker:
def __init__(self):
self.value = 1
self.log = []
def apply(self, x):
try:
self.value += x
if self.value > 4:
raise LimitError(self.value)
except LimitError as e:
self.value -= 2
self.log.append(f'fix:{e.args[0]}')
else:
self.log.append('ok')
finally:
self.value += 1
c = Tracker()
for n in (2, 3, -1):
c.apply(n)
print(c.value, ','.join(c.log))
Options:
A. 3 ok,fix:7,fix:5
B. 4 ok,fix:7,fix:5
C. 4 ok,ok,fix:5
D. 4 ok,fix:7,ok
Best answer: B
Explanation: The same Tracker object is used for all three calls, so its value carries forward each time. When value becomes greater than 4, the except block adjusts it and records the exception argument, and finally still adds 1.
This item is about tracing instance state across repeated method calls. c.value and c.log belong to the same object, so each apply() call starts from the previous call’s final state.
apply(2): value goes from 1 to 3, no exception occurs, else adds ok, then finally makes value 4.apply(3): value goes from 4 to 7, LimitError(7) is raised, except changes value to 5 and logs fix:7, then finally makes it 6.apply(-1): value goes from 6 to 5, another LimitError(5) is raised, except changes value to 3 and logs fix:5, then finally makes it 4.So the output is 4 ok,fix:7,fix:5. A common mistake is forgetting that finally runs even when an exception is handled.
3 overlooks that finally runs after the third handled exception and increments value again.ok on the third call ignores that the third call starts from value == 6, so adding -1 still triggers the exception.ok on the second call misses that else runs only when no exception is raised.Use the PCAP-31-03 Practice Test page for the full IT Mastery route, mixed-topic practice, timed mock exams, explanations, and web/mobile app access.
Try PCAP-31-03 on Web View PCAP-31-03 Practice Test
Read the PCAP-31-03 Cheat Sheet on Tech Exam Lexicon for concept review before another timed run.