Free Python Institute PCAP Practice Exam: Associate Python Programmer
Try 40 free Python Institute PCAP - Certified Associate Python Programmer (PCAP-31-03) questions across the exam domains, with explanations, then continue with IT Mastery practice.
This free full-length Python Institute PCAP practice exam includes 40 original IT Mastery questions across the exam domains.
These are original IT Mastery practice questions. They are not official Python Institute questions, copied live-exam content, or exam dumps. Use them to preview question style and explanation depth before continuing with mixed sets, topic drills, and timed mocks in IT Mastery.
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.
Try the IT Mastery web app for a richer interactive practice experience with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Exam snapshot
- Practice target: Python Institute PCAP
- Practice-set question count: 40
- Time limit: 65 minutes
- Practice style: mixed-domain diagnostic run with answer explanations
Full-length exam mix
| 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 interactive practice.
Practice questions
Questions 1-25
Question 1
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.
Bikeis a subclass ofVehicle.B.
Bikeis a superclass ofCar.C.
Vehicleis a superclass ofCar.D.
Vehicleis a subclass ofBike.E.
Caris a superclass ofVehicle.F.
Carinherits directly fromBike.
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):meansCaris a subclass ofVehicle.class Bike(Vehicle):meansBikeis a subclass ofVehicle.
A common mistake is to reverse the relationship or assume that two classes with the same parent inherit from each other.
- Reversed relationship The option making
Carthe superclass ofVehicleflips child and parent. - Reversed again The option making
Vehiclea subclass ofBikemisreads the inheritance syntax. - Sibling confusion The option saying
Carinherits fromBikeis wrong because both inherit fromVehicle. - Wrong parent-child pair The option making
Bikethe superclass ofCartreats sibling classes as if one inherited from the other.
Question 2
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.readF.
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.
- Too few characters the option using
read(11)leaves one character unread. - One line only the option using
readline()stops at the next newline, so it does not guarantee all remaining content. - Method reference the option using
stream.readdoes not call the method. - Invalid method the option using
readall()is not the normal file-object API in this PCAP context.
Question 3
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
Deviceclass is copied into two separate embedded objects insideAllInOne.
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.
- More than one parent can define the same method name.
- Python resolves that name using a deterministic method resolution order (MRO).
- The shared ancestor is still part of the lookup chain through multiple routes.
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.
- Automatic dual calls is wrong because a normal method call resolves to one method through MRO; Python does not call both parent methods by default.
- Copied base object is wrong because inheritance defines lookup relationships, not duplicated embedded
Deviceobjects. - No overriding allowed is wrong because subclasses in Python can still override methods in multiple-inheritance hierarchies.
- Random lookup is wrong because Python uses a deterministic resolution order, even in a diamond pattern.
Question 4
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 noteC.
note not in '#'D.
note != '#'E.
'.exe' not in filenameF.
'.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.
- The reversed membership check with
filename not in '.exe'tests whether the whole filename appears inside the literal.exe. - The check
note != '#'only asks whether the entire note is exactly#, not whether#appears inside it. - The check
'.exe' != filenamecompares full strings for inequality and does not perform a substring search. - The reversed form
note not in '#'searches in the wrong direction and does not test whether#is absent fromnote.
Question 5
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 ValueErrorbeforeexcept Exception.B. Move
int(v)outside thetryblock.C. Use
raiseinsideexcept Exceptionto letexcept ValueErrorrun.D. Replace
except Exceptionwithexcept 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.
- Put more specific exception classes first.
- Put broader fallback handlers later.
- Use a generic
except Exceptiononly as a last resort.
A common mistake is assuming Python will keep checking later handlers after entering a broad one, but it does not.
- Re-raise idea fails because raising from the broad handler sends the exception outward; it does not continue to a later sibling
except. - Broader catch with
BaseExceptionmakes the problem worse by catching even more cases before the specific handler. - Move conversion out would prevent this
tryblock from handling theValueErrorat all.
Question 6
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::WARNcontains::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.
- The option showing
IO::WARNassumes the first::was used instead of the last one. - The option showing
SYS::IOwould come from slicing before the last separator, not after it. - The option showing the full code ignores that
__str__()returns only a slice ofself.code.
Question 7
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.binwith'rt', storef.read()inbuf, then assignbuf[0] = '\xff'.B. Open
log.txtwith'rt', encoding='utf-8', buildbytearray(f.read()), then callcount('ERROR').C. Open
log.txtwith'rt', encoding='utf-8', keepf.read()asstr, then callcount('ERROR').D. Open
log.txtwith'rb', keepf.read()asbytes, then callcount('ERROR').E. Open
packet.binwith'rb', convertf.read()tobytearray, then assignbuf[0] = 255.F. Open
packet.binwith'rb', keepf.read()asbytes, then assignbuf[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.
- The binary-read log option is wrong because
bytesis not the right type for a text search using the string'ERROR'. - The text-read packet option is wrong because text mode produces decoded characters, not a raw byte buffer, and
stris immutable. - The binary-read packet option that keeps
bytesis wrong because indexed assignment likebuf[0] = 255is not allowed onbytes. - The text-read option that builds
bytearray(f.read())is wrong becausef.read()returns astrin text mode, and that conversion is not the right fit for a text-processing task.
Question 8
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().
- The option using
random.dir()fails becausediris not a module method. - The option using
rng.dir()fails because instances do not usediras an attribute-inspection method. - The option using
random()fails becauserandomin the snippet is the imported module, not a callable. - The option using
"rng"inspects a string object, not the existingrngvariable from the code.
Question 9
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, thenbytesB.
1 3, thenstrC.
1 1, thenbytesD.
1 3, thenbytes
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.
scontains one character:€b = s.encode("utf-8")creates abytesobject€uses 3 bytes in UTF-8, solen(s)is1andlen(b)is3- Because the lengths differ,
ValueError(1, 3)is raised and caught - The handler prints the exception arguments as
1 3 - The
finallyblock always runs and printsbytes
The closest mistake is assuming one character always equals one byte, which is not true for many non-ASCII Unicode characters.
- One-byte assumption fails because
€is one character but not one UTF-8 byte. - Both lengths as 3 fails because only the encoded
bytesobject has length3; the originalstrstill has length1. - Wrong result type fails because
encode()returnsbytes, notstr.
Question 10
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 addf.seek(0)beforereadline().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.
- Open with
a+. - Call
f.seek(0)to move from EOF to the start. - Read the first line with
readline(). - Write the uppercase text; append mode places it at the end.
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+withoutseek(0)starts reading at EOF, soreadline()returns an empty string.
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)
Options:
A. It raises
TypeErroronc.add(5)B. It prints
15C. It prints
10D. It raises
NameErroronself.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.
- The option claiming it prints
15assumes a correct instance-method signature; the missingselfprevents the method call from succeeding. - The option claiming it prints
10is wrong because the program stops atc.add(5)and never reachesprint(c.value). - The option claiming
NameErroroverlooks that Python detects the wrong argument count first, so the method body is not executed.
Question 12
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 mathB. 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.
- Changing only the import to
import mathleavessqrt(16)undefined, becausesqrtis no longer imported directly. - Prefixing both calls with
math.still fails, because the namemathdoes not exist afterfrom math import hypot, sqrt. - Passing
[6, 8]tohypotuses the wrong signature;hypotexpects numeric arguments, not a single list.
Question 13
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.
__traceis name-mangled to_settings__tracebecause double underscores always mangle names.B. Only
_retry_limitis skipped by wildcard import;__traceis imported because it uses two underscores.C.
timeoutis public;_retry_limitand__traceare non-public by convention and are skipped by wildcard import.D.
_retry_limitbecomes a class variable ofWorkerbecause 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 _.
- Name mangling mix-up fails because module-level
__traceis not name-mangled; name mangling applies to class attributes. - Class variable confusion fails because a module variable does not become part of a class just because it appears before the class definition.
- Double underscore import myth fails because wildcard import skips any name starting with
_, including names starting with__.
Question 14
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
UnsupportedOperationhandler runs,elseis skipped, andfinallystill runs.B.
elseruns because opening the file succeeded before thewrite()call.C. No exception is raised because
'r'mode allows both reading and writing.D. The grouped
(OSError, ValueError)handler runs first becauseUnsupportedOperationis also anOSError.
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.- The first matching
exceptruns. elseis skipped because an exception occurred.finallyruns 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.
- Broader handler first fails because
exceptblocks are checked in order, so the specificUnsupportedOperationblock catches the error before the grouped one. elseafter open fails becauseelseruns only when the entiretryblock completes without any exception.- Read mode writes fails because
'r'is read-only; writing requires a writable mode such as'w','a', or'r+'.
Question 15
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
namesto a tuple first.C. Replace
map()withfilter().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.
- Replacing
map()withfilter()fails becausefilter()keeps or discards items; it does not transform each string. - Converting
namesto a tuple does not change what the lambda returns. - Removing
list()would only leave amapiterator; the lambda would still produce method objects.
Question 16
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.
- The output keeping uppercase
Pignores that alphabetic characters are passed through.lower(). - The output without
-treats the space as if it were skipped, but the code explicitly replaces it. - The output preserving
3!ignores that nonletters that are not spaces fall into theelsebranch.
Question 17
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): passB.
class AmphibiousVehicle(LandVehicle): passC.
class AmphibiousVehicle(WaterVehicle): passD.
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:
LandVehicleis one direct base.WaterVehicleis the second direct base.Vehicleremains 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.
- Inheriting only from
Vehicleskips the requirement to derive from both specialized classes. - Inheriting only from
LandVehicleis still single inheritance, so the water-side parent is missing. - Inheriting only from
WaterVehiclehas the same problem in reverse.
Question 18
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 beforereader.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.
- The option using
find()fails because it returns the first dot, not the last one. - The option using
index()fails because it searches from the left and raisesValueErrorif no dot exists. - The option using
rindex()finds the last dot, but it still raisesValueErrorwhen the string has no dot.
Question 19
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 = levelB.
def __init__(name, level): self.name = name; self.level = levelC.
def __init__(self, name, level): User.name = name; User.level = levelD.
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.
- The option without
selfas the first parameter is invalid for normal instance initialization. - The option assigning through
User.nameandUser.levelcreates class variables, not instance variables. - The option reversing the assignment tries to read
self.nameandself.levelbefore they exist.
Question 20
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.numberandTicket.owner.B. Change the method to
def __str__(self, extra):.C. Replace
print(t)withprint(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):- return the desired string
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.
- Extra parameter fails because
__str__must be callable for the instance without an added argument. - Class attributes fail because
numberandownerwere stored on each instance, not on theTicketclass. - Changing
print()bypasses the real issue and does not define how the object should convert to a string.
Question 21
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
namebecauseself.nameis assigned in__init__.B. It stores the private attribute under the key
__limit.C. It contains
category,_Report__limit,__init__, andshow.D. The value of
Report.__dict__['show']is the bound method fromr.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.
- The option claiming
nameappears inReport.__dict__confuses an instance attribute with a class attribute. - The option using
__limitunchanged ignores Python name mangling for double-underscore class names. - The option treating
showas already bound misses that binding happens when the function is accessed throughr.
Question 22
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, thenmainB.
tools, then__main__C.
__main__, then__main__D.
Hammer, thenNail
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.
- The option using
tools, thenmainmisses that a directly executed script has module name__main__. - The option using
__main__for both lines incorrectly assumes importing a class changes its defining module. - The option using
Hammer, thenNailconfuses__module__with class names such as__name__.
Question 23
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.
AttributeErroris raisedC. 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.
- The option saying
Topignores thatRight.name()appears earlier thanTop.name()inBottom’s lookup path. - The option saying
Leftconfuses the class that providesshow()with the class that providesname(). - The exception option fails because a
name()method does exist in the inheritance hierarchy, so lookup succeeds.
Question 24
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.nameB.
def label(): return self.nameC.
def label(self): return self.nameD.
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.
- The option with no parameters fails because
u.label()still passes the instance, causing aTypeError. - The option using
objwould run, but it does not satisfy the requestedselfnaming convention. - The option requiring
valuefails because the call provides only the bound instance, not a second argument.
Question 25
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")withtext.decode("utf-8").B. Replace
restored = packet.decode("utf-8")withrestored = str(packet).C. Replace
text.encode("ascii")withascii(text).encode("utf-8").D. Replace
text.encode("ascii")withtext.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:
- start with Unicode text in a
str - convert it to bytes with
encode("utf-8") - convert the bytes back with
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.
- Using
ascii(text)fails because it turns non-ASCII characters into escape sequences, changing the original content. - Calling
decode()ontextfails becausedecode()is forbytes, whiletextis already a Unicodestr. - Converting
packetwithstr(packet)fails because it produces a representation likeb'...', not the recovered text.
Questions 26-40
Question 26
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,valueandshowbecome local names, increasing collision risk;_hiddenandhelpersdo not.B. In
main.py, public names are imported only when first used.C. In
main.py,value,_hidden,show, andhelpersall become local names.D. In
main.py, onlyhelpersbecomes 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.
- The option claiming only
helpersis imported confusesfrom helpers import *withimport helpers. - The option claiming
_hiddenis also imported ignores the default rule that skips leading-underscore names when__all__is absent. - The option claiming names are imported only when first used is wrong because the namespace is populated when the import statement executes.
Question 27
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.
- With
2,10 // 2succeeds, so the trace isT,L,F. - With
0,10 // 0raisesZeroDivisionError, so the trace isT,E,F. - After both calls, the final
printadds!.
That produces TLFTEF!. The closest mistake is skipping the second F, but finally still runs after the exception is handled.
- The option missing the second
Ffails becausefinallystill runs afterZeroDivisionErroris caught. - The option starting with
TEFfails because the loop processes2before0. - The option with
FFtogether fails becausefinallyruns once per function call, immediately afterelseorexcept.
Question 28
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 existingexcept 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.
- Catching only
ConfigErroris too narrow because the requirement includes future exceptions derived fromAppError. - Adding a specific handler before the existing broad
except Exceptionstill leaves unrelated built-in exceptions caught by the generic block. - Using a tuple that includes
Exceptionremains too broad, so built-in errors likeValueErrorare still caught accidentally.
Question 29
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-PC.
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.
- After
P,outisP- - After
C,outisP-C- - At the space,
outstaysP-C- - After
AandP,outbecomesP-C-A-P-
The key takeaway is that direct string iteration works character by character, including spaces unless your code skips them.
- The option showing
PC-AP-treats the string like two chunks, but direct iteration returns single characters. - The option keeping the space ignores that
continueskips the append step whenchis' '. - The option without the final hyphen misses that
ch + "-"runs for every non-space character, including the lastP.
Question 30
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__returnsZeroDivisionErrorerr.argsis a tuple, soerr.args[0]isdivision by zerofinallyruns whether or not an exception was raised
So the program prints the exception type and message, then prints done. The closest wrong choice confuses the whole args tuple with its first element.
- Wrong exception the option showing
ValueErrorfails becauseint("0")does not raise an exception. - Tuple confusion the option showing
('division by zero',)useserr.args, but the code printserr.args[0]. - Missing cleanup the one-line option ignores that
finallyalways runs after the handled exception.
Question 31
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")intofinallyB. Make
_str_returnstr(self.code)C. Catch
Exceptioninstead ofAssertionErrorD. 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 printsbad outputelse: skipped because an exception occurredfinally: always runs and printsdone
Renaming _str_ to __str__ fixes the object’s string conversion behavior; changing handlers or finally does not fix the underlying class problem.
- Making
_str_returnstr(self.code)still leaves the special method misspelled, sostr(t)never calls it. - Catching
Exceptionchanges the handler, but the assertion still fails and theelseblock still does not run. - Moving
print("ok")intofinallychanges output placement, not the broken string-conversion behavior.
Question 32
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 += valuewithWallet.amount += value.B. Replace
amount += valuewithself.amount += value.C. Change
def add(self, value):todef add(value, self):.D. Replace
amount += valuewithamount = 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)setsw.amountto10w.add(5)changesw.amountto15w.spend(3)changesw.amountto12
The key point is to update the instance attribute, not a local name or a class attribute.
- Class vs instance Using
Wallet.amounttargets a class attribute, not this object’s stored balance. - Local assignment Writing
amount = self.amount + valueonly creates a local variable unless the result is assigned back toself.amount. - Wrong signature Swapping the parameter order breaks normal instance method binding, because Python passes the instance as the first argument.
Question 33
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.
- The option with all three lines ignores that the first line was already consumed by
readline(). - The single string option confuses
readlines()withread(), which returns one string. - The list without
\nassumes line endings are stripped automatically, butreadlines()preserves them here.
Question 34
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;4B.
2;3 3;3C.
2;4;3D.
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]is4because that was passed to the exception.- The handler executes
c.value -= 1, so the object’s state becomes3. - The
finallyblock always runs and prints the current value, which is3.
The key point is that the method updates object state first, and the exception handler updates it again afterward.
- The option showing
3 3treats the exception argument as if it changed after the handler modified the object. - The option showing
4 4ignores thec.value -= 1statement inside theexceptblock. - The option showing only
4before the last line misses thatprint(e.args[0], c.value)outputs two values on one line.
Question 35
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.
ZeroDivisionErroroccurs first, so the handler order is wrong.B. The
tryblock protects only the division line.C.
int("abc")raisesValueError, and no handler catches it.D.
record["age"]raisesKeyErrorbecause 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.
KeyErrorwould be caught only if"age"were missing.ZeroDivisionErrorwould be caught only if conversion succeeded and the value became0.
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.
- Missing key confusion fails because the
"age"key exists; the problem is converting its value. - Wrong operation order fails because division cannot run until the integer conversion succeeds.
- Try scope misunderstanding fails because every indented statement inside the
trysuite is protected.
Question 36
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 helperwithimport helper.B. Delete the package
__pycache__directories.C. Put
"/proj/lib"before"/legacy"insys.path.D. Add
"/proj/lib/tools"tosys.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.
- Adding
"/proj/lib/tools"uses the package directory instead of its parent, sofrom tools import helperstill would not locatetoolscorrectly. - Deleting
__pycache__may force recompilation, but it does not change which matching package is found first. - Replacing the import with
import helperlooks for a top-level module namedhelper, not thehelpersubmodule insidetools.
Question 37
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]
[2, 3] C. cube [1, 8] lambda [2, 3]
D.
[1, 8] [2, 3]
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.
- The option showing
lambdawithout angle brackets fails because Python reports a lambda function name as<lambda>. - The option showing
<lambda>on both lines fails because the first call uses the named functioncube. - The option showing
cubeon both lines fails because the second call passes a different inline function, notcube.
Question 38
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].
- For
1, the lambda returnsFalse - For
2, it returnsTrue, so20is appended - For
3, it returnsFalse - For
4, it returnsTrue, so40is appended
The 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.
- The option showing only
[2, 4]misses that the function appendsv * 10, notv. - The option showing all four multiplied values ignores the
if test(v)condition. - The option showing booleans confuses the lambda’s return values with the values added to
result.
Question 39
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.
savedthenABB.
TypeErrorthenABC.
encode text before binary writethenABCD.
encode text before binary writethenAB
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.
- The option with
savedfails because the secondwriteraises an exception, so the innerelseblock does not run. - The option with
TypeErrorfails because that exception is caught and replaced withBinaryDataError. - The option ending with
ABCfails because writing to the file does not modify the originalbytearray.
Question 40
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:5B.
4 ok,fix:7,fix:5C.
4 ok,ok,fix:5D.
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.
- After
apply(2):valuegoes from 1 to 3, no exception occurs,elseaddsok, thenfinallymakesvalue4. - After
apply(3):valuegoes from 4 to 7,LimitError(7)is raised,exceptchangesvalueto 5 and logsfix:7, thenfinallymakes it 6. - After
apply(-1):valuegoes from 6 to 5, anotherLimitError(5)is raised,exceptchangesvalueto 3 and logsfix:5, thenfinallymakes 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.
- The option with final value
3overlooks thatfinallyruns after the third handled exception and incrementsvalueagain. - The option ending with
okon the third call ignores that the third call starts fromvalue == 6, so adding-1still triggers the exception. - The option showing
okon the second call misses thatelseruns only when no exception is raised.
Continue in the web app
Use IT Mastery for interactive Python Institute PCAP practice with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Try Python Institute PCAP on Web