Courseiva

Certified Associate Python Programmer PCAP (PCAP) — Questions 76–150

421 questions total · 6pages · All types, answers revealed

Page 1

Page 2 of 6

Page 3
76
MCQmedium

A developer is designing a class hierarchy for a library system. They want to ensure that a method 'borrow' in the base class 'Item' can be overridden by subclasses like 'Book' and 'DVD', but the base implementation should not be callable directly. Which approach best achieves this?

A.Define borrow with raise NotImplementedError and override in subclasses
B.Define borrow as a static method and override in subclasses
C.Define borrow as a class method and override in subclasses
D.Define borrow with pass and let subclasses override
AnswerA

Correct: defining borrow to raise NotImplementedError creates an explicit contract in which the base class provides no usable behavior. If a concrete subclass forgets to override it, calling that method fails loudly at runtime instead of silently returning a meaningless value. Subclasses that override borrow participate in normal polymorphic dispatch, so callers can treat all library materials uniformly through a base-class reference. This is the classic Python idiom for a semi-abstract method when the abc module is not used.

Why this answer

Raising `NotImplementedError` in the base class `Item.borrow` makes the method abstract in practice: it cannot be called directly without causing an error, forcing subclasses like `Book` and `DVD` to provide their own override. This pattern enforces that the base implementation is never invoked accidentally, while still allowing polymorphic dispatch through inheritance.

Exam trap

Python Institute often tests the distinction between preventing base class instantiation versus preventing base method invocation — candidates mistakenly think `pass` or a static method achieves the same effect, but only raising `NotImplementedError` ensures the base method cannot be called directly.

How to eliminate wrong answers

Option B is wrong because defining `borrow` as a static method prevents it from receiving the instance (`self`) or class (`cls`) reference, making it unsuitable for polymorphic override in a class hierarchy where instance-specific behavior is needed. Option C is wrong because a class method receives the class as the first argument, not the instance, which breaks the typical override pattern for instance methods like `borrow` that depend on per-object state (e.g., a specific book's availability). Option D is wrong because defining `borrow` with `pass` provides a silent no-op default that can be called directly without error, failing the requirement that the base implementation should not be callable directly.

77
MCQmedium

A network engineer processes a configuration file containing MAC addresses in the format 'aa:bb:cc:dd:ee:ff'. They need to convert each MAC address into a 6-byte bytes object for use in packet crafting. The current code is: mac_bytes = bytes([int(x, 16) for x in mac_str.split(':')]). This works correctly, but they need to process thousands of MAC addresses and want to optimize performance. They also need to handle invalid MAC addresses (e.g., non-hex characters) without crashing. Which of the following approaches is the most efficient and robust?

A.Use the same list comprehension but add a try-except block for ValueError
B.Use bytes.fromhex(mac_str.replace(':', ''))
C.Use struct.pack('BBBBBB', *[int(x,16) for x in mac_str.split(':')])
D.Use a for loop to parse each pair and build a bytearray
AnswerB

bytes.fromhex() is a built-in method implemented in C that parses a hex string directly into a bytes object, making it the fastest and most idiomatic choice. Removing the colons with .replace(':', '') yields a 12-character hex string, which fromhex converts to exactly six bytes. It also performs validation in the C layer: non-hex characters or odd-length strings raise ValueError, giving the same error behavior as a manual parse but without Python-level iteration.

Why this answer

`bytes.fromhex()` is implemented in C, making it significantly faster than a Python-level list comprehension for thousands of conversions. It also inherently validates that the input contains only hexadecimal characters (and colons, which are ignored after removal), raising a `ValueError` for invalid input, which can be caught for robustness. This approach avoids the overhead of splitting, iterating, and calling `int()` for each octet.

Exam trap

The PCAP exam often tests the misconception that a list comprehension or `struct.pack` is the most efficient approach, when in reality Python's built-in `bytes.fromhex()` leverages C-level optimization for both speed and validation.

How to eliminate wrong answers

Option A is wrong because while it adds error handling, it still uses the slower list comprehension with `int(x, 16)` for each octet, which involves Python-level iteration and function calls, making it less efficient than the C-level `bytes.fromhex()`. Option C is wrong because `struct.pack()` adds unnecessary overhead by requiring the list comprehension to produce the integers first, then packing them into bytes; it is neither the most efficient nor the most direct method. Option D is wrong because a manual for loop with `bytearray` is the slowest approach, as it involves Python-level iteration, multiple function calls, and incremental appending, which is far less efficient than the single C-level call in Option B.

78
MCQmedium

A developer generates a report where numbers must be right-aligned in a 10-character column using f-strings: f'{value:>10}'. However, some values may be None, causing a TypeError. Which is the most robust way to handle None values without affecting other falsy values like 0?

A.Use str.format() with a conditional for the format spec
B.f'{value or "N/A":>10}'
C.f'{value if value is not None else "N/A":>10}'
D.Wrap the f-string in a try-except block
AnswerC

This option uses a conditional expression that explicitly tests identity with `is not None`, meaning only `None` triggers the fallback while preserving all other values, including 0 and empty strings. The f-string then applies the `:>10` format spec to the selected result, right-aligning either 'N/A' or the numeric value in a 10-character field. It is the only choice that correctly distinguishes a missing sentinel from legitimate falsy data.

Why this answer

It uses an explicit identity check (`value is not None`) to distinguish `None` from other falsy values like `0` or empty strings. This ensures that `0` is still right-aligned as a number, while `None` is replaced with the string `"N/A"` before formatting. The f-string then applies the `>10` alignment specifier to the resulting value.

Exam trap

The PCAP exam often tests the distinction between identity checks (`is None`) and truthiness checks (`or`, `if value`) to catch candidates who assume all falsy values should be treated equally, especially when `0` is a valid numeric value that must be preserved.

How to eliminate wrong answers

Option A is wrong because `str.format()` with a conditional for the format spec does not inherently handle `None` values; it would still raise a `TypeError` when trying to format `None` unless the conditional also replaces the value itself. Option B is wrong because `value or "N/A"` treats `0` (a falsy number) as `None`, incorrectly replacing it with `"N/A"` instead of preserving it for right-alignment. Option D is wrong because wrapping the f-string in a `try-except` block is a reactive approach that catches the `TypeError` at runtime, but it is less robust and less readable than a proactive conditional check; it also requires additional logic to decide what to display on exception.

79
MCQmedium

What will be printed?

A.-1
B.0
C.An exception is raised.
D.None
AnswerD

The correct output is None because `int("abc")` raises a ValueError, which is caught by the matching `except ValueError` block. That handler assigns the result variable to None, and when the code later prints that variable, Python displays the string representation of None (i.e., "None").

Why this answer

None. This question likely tests that Python functions return None by default when no explicit return statement is present, and printing that value yields None. Therefore, option D is correct.

Exam trap

Candidates often mistake the absence of a return statement for an implicit return of 0 or -1, or think an exception is raised. However, Python functions default to returning None, and printing that yields 'None'.

How to eliminate wrong answers

Option A is wrong because -1 is never printed; the code does not contain any print statement that would output -1, and the exception prevents any output. Option B is wrong because 0 is never printed; the code does not contain any print statement that would output 0, and the exception prevents any output. Option C is wrong because while an exception is raised, the question asks 'What will be printed?' and the answer is that nothing is printed (None), not that an exception is raised as the printed output.

80
MCQeasy

Which of the following statements about the `finally` block is true?

A.It executes only if no exception is raised.
B.It does not execute if a return statement is in try block.
C.It executes only if an exception is raised.
D.It always executes, regardless of exceptions.
AnswerD

The `finally` block guarantees execution after the `try` block completes, whether an exception is raised, caught, or the block exits via `return`, `break` or `continue`. This satisfies the stem's requirement for a statement that is always true, since only `finally` provides unconditional cleanup semantics in Python.

Why this answer

The `finally` block in Python is designed to always execute after the `try` and `except` blocks, regardless of whether an exception was raised or not. This includes cases where a `return`, `break`, or `continue` statement is executed in the `try` block, or even if an unhandled exception occurs. The `finally` block is guaranteed to run before the function returns or the exception propagates, ensuring cleanup actions like closing files or releasing resources.

Exam trap

Python Institute often tests the misconception that a `return` statement in the `try` block prevents the `finally` block from executing, but in Python, the `finally` block always runs before the function returns, making this a common trap for candidates who confuse Python's behavior with that of other languages.

How to eliminate wrong answers

Option A is wrong because the `finally` block executes regardless of whether an exception is raised, not only when no exception occurs. Option B is wrong because the `finally` block does execute even if a `return` statement is in the `try` block; the `finally` block runs before the function actually returns. Option C is wrong because the `finally` block executes regardless of whether an exception is raised, not only when an exception occurs.

81
MCQeasy

A developer wants to create a class that logs every attribute access on an instance. Which special method should they override?

A.`__getattr__`
B.`__getattribute__`
C.`__setattr__`
D.`__delattr__`
AnswerB

Overriding `__getattribute__` intercepts every attribute lookup on an instance, including those resolved via `__getattr__`, making it the only hook that fires for all accesses. `__getattr__` runs solely when normal lookup fails, so it cannot log successful reads. This satisfies the requirement to log every attribute access.

Why this answer

`__getattribute__` is the special method that is called unconditionally for every attribute access on an instance, making it the appropriate choice for logging all attribute accesses. Overriding this method allows the developer to intercept and log each access before the attribute is retrieved, whereas `__getattr__` is only invoked when the attribute is not found via normal lookup.

Exam trap

The trap here is that candidates confuse `__getattr__` (called only on missing attributes) with `__getattribute__` (called on every access), and Python Institute often tests this distinction by presenting a scenario requiring unconditional interception.

How to eliminate wrong answers

Option A is wrong because `__getattr__` is only called when an attribute is not found through the normal lookup mechanism (i.e., when `__getattribute__` raises an AttributeError), so it would not log every attribute access, only failed ones. Option C is wrong because `__setattr__` is called on attribute assignment, not access, so it cannot log reads. Option D is wrong because `__delattr__` is called on attribute deletion, not access, and is irrelevant to logging accesses.

82
MCQmedium

What does the expression 'hello world'.title() return?

A.'Hello World'
B.'HELLO WORLD'
C.'Hello world'
D.'hello World'
AnswerA

'Hello World' is the exact return value of 'hello world'.title(): the title() method scans the string and, for each whitespace-delimited word, converts its first character to uppercase while converting any remaining characters to lowercase. Since both original words are already lowercase, each word becomes capitalized, yielding 'Hello World'.

Why this answer

The `title()` method in Python returns a copy of the string where the first character of each word is converted to uppercase and all remaining characters are converted to lowercase. For the string 'hello world', this results in 'Hello World', making option A correct.

Exam trap

Python Institute often tests the distinction between `title()`, `capitalize()`, and `upper()` by presenting strings where only one word is capitalized, leading candidates to confuse the behavior of these methods.

How to eliminate wrong answers

Option B is wrong because `title()` does not convert all characters to uppercase; that would be the behavior of the `upper()` method. Option C is wrong because it only capitalizes the first word, which is what `capitalize()` does, not `title()`. Option D is wrong because it capitalizes only the second word, which is not how `title()` operates; `title()` capitalizes the first character of every word.

83
Matchingmedium

Match each string method to its purpose.

Drag a concept onto its matching description — or click a concept then click the description.

Concepts
Matches

Returns uppercase copy

Splits into list of substrings

Removes leading/trailing whitespace

Replaces occurrences of a substring

Returns index of first occurrence

Why these pairings

The correct matches are: .upper() converts to uppercase, .lower() converts to lowercase, .strip() removes leading/trailing whitespace. Common confusions include swapping .split() and .replace(), and confusing .replace() with .upper().

84
MCQmedium

A Python script that processes log files uses the following code: with open('log.txt', 'r') as f: lines = f.readlines() for line in lines: # process What is a potential inefficiency in this code?

A.The file is not properly closed after processing.
B.Reading the entire file into memory may be wasteful for large files.
C.Using readlines() is the most efficient way to iterate over lines.
D.The file should be opened in binary mode for better performance.
AnswerB

This is the correct statement. Calling `readlines()` reads every line of the file into a list of strings, meaning the entire file content is loaded into memory at once. For large log files, this can consume a massive amount of RAM, potentially causing the program to slow down or crash with a MemoryError. A more memory-efficient approach is to iterate directly over the file object (`for line in f:`), which reads one line at a time lazily, keeping the memory footprint low regardless of file size.

Why this answer

`readlines()` loads the entire file into memory as a list of strings. For large log files, this can consume significant memory and cause performance degradation or even memory errors. A more memory-efficient approach is to iterate directly over the file object (e.g., `for line in f:`), which reads one line at a time from disk.

Exam trap

Python Institute often tests the misconception that `readlines()` is the standard or recommended way to read a file line by line, when in fact the file object itself is an iterator that should be used for large files to avoid memory bloat.

How to eliminate wrong answers

Option A is wrong because the `with` statement ensures the file is automatically closed when the block exits, even if an exception occurs. Option C is wrong because `readlines()` is not the most efficient way to iterate over lines; it reads all lines into memory at once, whereas iterating over the file object directly is more memory-efficient. Option D is wrong because opening in binary mode (`'rb'`) would not improve performance for text log processing and would require manual decoding of bytes to strings, adding complexity without benefit.

85
MCQeasy

A class defines an __init__ method that takes optional arguments. What is the correct way to provide default values?

A.Use class variables to store defaults.
B.Use default parameter values in the __init__ signature.
C.Override __new__ to set default values.
D.Use a separate setter method called after instantiation.
AnswerB

Defining default parameter values in the __init__ signature is the canonical Python idiom for optional constructor arguments. These defaults are evaluated once at function definition time, but for immutable types (like None, int, str) that is harmless because rebinding a parameter simply rebinds the local name. This approach requires no extra code, allows callers to omit the argument or pass it by keyword, and keeps all initialization logic inside the constructor.

Why this answer

Python's `__init__` method, like any other function, supports default parameter values in its signature. This is the idiomatic and simplest way to provide default values for instance attributes, as the defaults are evaluated at function definition time and assigned to the parameter when no argument is provided.

Exam trap

The PCAP exam often tests the mutable default argument pitfall — candidates may incorrectly think that using a mutable default (like `[]` or `{}`) is safe, or they may confuse class variables with instance defaults, leading them to choose option A.

How to eliminate wrong answers

Option A is wrong because class variables are shared across all instances; mutating a default value stored as a class variable (e.g., a list) would affect all instances, which is not the intended behavior for per-instance defaults. Option C is wrong because overriding `__new__` is unnecessary and overly complex for setting default values; `__new__` is responsible for creating the instance, not for initializing attributes, and using it for defaults would be non-idiomatic and error-prone. Option D is wrong because relying on a separate setter method called after instantiation forces the caller to remember to invoke it, breaking the encapsulation and convenience that `__init__` provides; it also does not constitute a default value mechanism within the constructor itself.

86
MCQhard

What is the value of matches?

A.['Alice', 'Bob']
B.['Alice']
C.['Alice', 'and', 'Bob', 'are', 'friends']
D.['Alice', 'Bob', 'friends']
AnswerA

This is correct because the regex pattern likely uses a character class such as [A-Z] to require an initial uppercase letter followed by word characters, and `re.findall()` returns every non-overlapping match in the string. Both 'Alice' and 'Bob' begin with uppercase letters and consist entirely of alphabetic characters, so they are the complete set of matching substrings. No other word in the input starts with an uppercase letter, so no additional tokens qualify.

Why this answer

The `re.findall(r'[A-Z][a-z]*', 'Alice and Bob are friends')` call matches all sequences starting with an uppercase letter followed by zero or more lowercase letters. This yields 'Alice' and 'Bob', as they are the only words beginning with a capital letter. The result is a list of those two strings.

Exam trap

Python Institute often tests the misconception that `[a-z]*` matches any sequence of letters, but the pattern requires the first character to be uppercase, causing candidates to incorrectly include all words or miss the second capitalized word.

How to eliminate wrong answers

Option B is wrong because it omits 'Bob', which also starts with an uppercase 'B' and matches the pattern. Option C is wrong because it includes all words from the string, but the pattern only matches words starting with an uppercase letter, not lowercase words like 'and', 'are', 'friends'. Option D is wrong because it includes 'friends', which starts with a lowercase 'f' and does not match the pattern `[A-Z][a-z]*`.

87
Multi-Selectmedium

Which TWO of the following statements about Python classes are true? (Select exactly 2.)

Select 2 answers
A.Class names should be written in snake_case per PEP 8.
B.Class variables are shared among all instances.
C.Private attributes (starting with __) cannot be accessed outside the class.
D.__init__ is the constructor of a class.
E.Instance methods must have 'self' as the first parameter.
AnswersB, E

Class variables are bound to the class namespace rather than to individual instances, so every instance shares the same variable. If the variable is reassigned on an instance, that creates a separate instance-level attribute that shadows the shared class variable, but the class variable itself remains unchanged for other instances. This shared behavior is a fundamental distinction from instance attributes, which are defined inside __init__ or other methods.

Why this answer

Class variables are defined directly in the class body and are shared across all instances of that class. When you modify a class variable through the class itself, the change is reflected in every instance, as the variable is stored in the class's __dict__ rather than in each instance's __dict__.

Exam trap

Python Institute often tests the misconception that __init__ is the constructor (it is actually __new__) and that double-underscore attributes are truly private (they are only name-mangled, not inaccessible).

88
MCQeasy

A junior developer is writing a helper that reads a small log file and returns its contents as a single string. The file is guaranteed to exist and be readable. The helper currently uses an explicit open, a try/finally block, and manual close call. A reviewer asks for the most idiomatic Python 3 replacement that guarantees closure. Which construct should be used?

A.f = open('app.log'); data = f.read(); f.close(); return data
B.with open('app.log') as f: return f.read()
C.data = open('app.log').read(); return data
D.f = open('app.log'); try: return f.read(); finally: pass
AnswerB

The with statement is a context manager that calls close on the file object when the block exits, whether normally or through an exception. It replaces the manual try/finally and close calls with a single readable construct. This is the idiomatic Python 3 approach and directly satisfies the reviewer's request for guaranteed closure.

Why this answer

The with statement uses the context manager protocol, invoking __exit__ on the file object so close runs even when an exception propagates out of the block. That makes it the idiomatic replacement for manual try/finally plus close. The alternatives either skip cleanup on exceptions, depend on garbage collection, or contain an empty finally that releases nothing.

Exam trap

The trap here is treating a plain open followed by a close as equivalent to the with statement, forgetting that an exception between them bypasses the close call entirely.

89
MCQhard

Which of the following is a correct use of the @property decorator to create a getter and setter for an attribute named 'score' that ensures score stays between 0 and 100?

A.@property def _score(self): return self.score @_score.setter def _score(self, value): self.score = value
B.@property def score(self): return self.score @score.setter def score(self, value): self.score = value
C.def get_score(self): return self._score def set_score(self, value): self._score = value score = property(get_score, set_score)
D.@property def score(self): return self._score @score.setter def score(self, value): if 0 <= value <= 100: self._score = value
AnswerD

This is the canonical property pattern: the getter returns the private `_score` attribute, and the setter validates the incoming `value` before assigning it to `_score`. By using the backing field rather than the public property name, the code avoids recursion and gives the property exclusive control over reads and writes. When the validation condition fails, the setter silently refuses to update, keeping `_score` unchanged and enforcing the 0–100 range.

Why this answer

It uses the @property decorator to define a getter method that returns the private attribute `self._score`, and a setter method that validates the new value is between 0 and 100 before assigning it to `self._score`. This ensures encapsulation and data validation, which is the intended use of properties in Python.

Exam trap

The PCAP exam often tests the distinction between using the property name itself (causing recursion) versus a private backing attribute, and the requirement that the setter must include validation logic to satisfy constraints like range checks.

How to eliminate wrong answers

Option A is wrong because it uses `self.score` inside the getter and setter, which would cause infinite recursion (the getter calls itself) and does not store the value in a private attribute. Option B is wrong for the same reason: the getter returns `self.score`, which calls the getter again, leading to recursion; also the setter assigns to `self.score`, causing infinite recursion. Option C is wrong because it uses the traditional `property()` function with getter and setter methods, which is valid Python but does not use the @property decorator as required by the question; it also lacks validation logic to ensure the score stays between 0 and 100.

90
Multi-Selectmedium

Which TWO of the following string methods return a new string with all characters converted to lowercase? (Select exactly two.)

Select 2 answers
A.str.title()
B.str.swapcase()
C.str.capitalize()
D.str.lower()
E.str.casefold()
AnswersD, E

str.lower() is a correct answer because it returns a new string in which every Unicode character that has a lowercase mapping is converted to its lowercase equivalent, while characters without a case mapping remain unchanged. This method performs a simple, one-to-one lowercase conversion that is sufficient for many case-insensitive comparisons and is the standard way to normalize text to lowercase.

Why this answer

Str.lower(), is correct because it returns a new string with all Unicode characters converted to lowercase according to the current locale's case mapping. Option E, str.casefold(), is correct because it returns a string suitable for case-insensitive comparisons by applying aggressive folding that handles special cases like the German 'ß' (which becomes 'ss'), going beyond simple lowercase conversion.

Exam trap

Python Institute often tests the distinction between str.lower() and str.casefold() by presenting both as correct answers, trapping candidates who think casefold() only does lowercase conversion, when in fact it performs a more aggressive Unicode folding that also results in a lowercase string.

91
MCQeasy

A class `Circle` has a class attribute `pi = 3.14`. An instance `c = Circle()` sets `c.pi = 3.14159`. What is the value of `Circle.pi` after this assignment?

A.3.14
B.An AttributeError is raised because class attributes cannot be shadowed.
C.The value is undefined because class attributes are immutable.
D.3.14159
AnswerA

Class attributes are shared across all instances unless overridden by an instance attribute. When `c.pi = 3.14159` is executed, Python creates an instance attribute `pi` on `c`, shadowing the class attribute for that instance. The class attribute `Circle.pi` is not modified and retains its original value of 3.14.

Why this answer

In Python, attribute assignment on an instance always creates or modifies an instance attribute, even if a class attribute with the same name exists. The class attribute is only modified when assigned via the class itself, such as `Circle.pi = value`. Here, `c.pi = 3.14159` adds an instance attribute to `c`, leaving `Circle.pi` at 3.14.

Exam trap

The trap here is thinking that assigning to an instance attribute with the same name as a class attribute changes the class attribute.

92
MCQmedium

A class has both `@classmethod` and `@staticmethod` decorators. What is a key difference between them?

A.A classmethod cannot be called on an instance.
B.A classmethod receives the class as first argument.
C.A staticmethod must be called from the class only.
D.A classmethod cannot access class variables.
AnswerB

A classmethod is bound to the class and receives it implicitly as the first argument (conventionally cls), whereas a staticmethod receives no implicit first argument at all. This binding difference is the key distinction the question targets.

Why this answer

The key difference is that a `@classmethod` receives the class itself as the first implicit argument (conventionally named `cls`), allowing it to access or modify class-level state, while a `@staticmethod` receives no implicit first argument and behaves like a plain function, unable to access the class or instance. This makes option B correct because it accurately describes the distinguishing feature of a classmethod.

Exam trap

Python Institute often tests the misconception that classmethods cannot be called on instances, leading candidates to incorrectly select option A, when in fact they can be called on instances and still receive the class as the first argument.

How to eliminate wrong answers

Option A is wrong because a classmethod can be called on an instance; Python automatically passes the class of the instance as the first argument. Option C is wrong because a staticmethod can also be called on an instance, not only from the class; it simply does not receive any implicit first argument. Option D is wrong because a classmethod can access class variables via the `cls` parameter; it is specifically designed for that purpose.

93
MCQhard

A senior developer in a team argues that using try-except blocks is slower than checking conditions with if statements. They propose replacing all try blocks that handle file I/O errors with existence checks using os.path.exists before opening files. During a code review, you recall that Python's official documentation and best practices prefer EAFP (Easier to Ask for Forgiveness than Permission) over LBYL (Look Before You Leap) in many cases, especially in concurrent environments. The team's application is a multi-threaded web server that serves static files from a shared directory. Which is the strongest counterargument against the senior developer's proposal?

A.if statements are harder to read and maintain.
B.try-except can catch multiple exception types more cleanly.
C.try-except blocks have no performance cost at all.
D.LBYL leads to race conditions in concurrent code because the file's state can change between the check and the use.
AnswerD

This is the classic time-of-check-to-time-of-use (TOCTOU) race: in a multithreaded server, two threads can evaluate `os.path.exists(path)` at nearly the same instant, and then one thread may delete or replace the file before the other actually opens it. The check and the use are not atomic, so LBYL gives a false sense of safety. EAFP, by contrast, wraps the open itself in a try-except, handling the failure exactly when it occurs and eliminating the gap.

Why this answer

In a multi-threaded web server, the LBYL approach (checking with os.path.exists) introduces a classic TOCTOU (Time of Check, Time of Use) race condition: between the existence check and the actual file open, another thread could delete or rename the file, causing the open to fail despite the check passing. Python's EAFP idiom (try-except) avoids this window by attempting the operation directly and handling the exception if it fails, which is inherently atomic with respect to the file system state. This is why official Python documentation recommends EAFP over LBYL in concurrent environments.

Exam trap

Python Institute often tests the misconception that try-except is purely about style or performance, when in reality the critical exam point is that LBYL introduces race conditions in concurrent code, making EAFP the safer and recommended pattern.

How to eliminate wrong answers

Option A is wrong because readability is subjective and not the strongest technical counterargument; if statements can be written clearly, and the core issue is correctness, not style. Option B is wrong because while try-except can catch multiple exception types cleanly, this is a convenience feature and does not address the fundamental race condition problem in concurrent file access. Option C is wrong because try-except blocks do have a small performance cost when an exception is raised (though negligible in I/O-bound code), but the claim that they have 'no performance cost at all' is factually incorrect and misses the point that the primary concern is correctness, not micro-optimization.

94
Multi-Selectmedium

Which THREE of the following are true about Python strings?

Select 3 answers
A.They support slicing.
B.They are stored as arrays of ASCII characters.
C.They can be concatenated with the + operator.
D.They are mutable.
E.They support indexing.
AnswersA, C, E

Slicing is supported because strings are sequences: using the notation s[start:stop:step] you can extract a contiguous substring or even a reversed copy. The result is always a new string object, since the original cannot be modified in place, and the slice can use negative indices to count from the end, such as s[-3:] taking the last three characters.

Why this answer

Python strings are sequences, and the slicing syntax (e.g., s[start:stop:step]) allows extracting substrings by specifying indices. This works because strings implement the sequence protocol, including __getitem__ with slice objects.

Exam trap

Python Institute often tests the immutability of strings by presenting operations that appear to modify them in place, leading candidates to incorrectly select 'mutable' because they confuse string methods (like .replace() or .upper()) with in-place mutation.

95
MCQhard

What is the result of 'abcdef'[::-2]?

A.'dfb'
B.'ace'
C.'fdb'
D.'eca'
AnswerC

'fdb' is the correct result of the slice 'abcdef'[::-2]. The negative step tells Python to traverse the sequence backward from the last character, selecting 'f' (index 5), then 'd' (index 3), then 'b' (index 1). This is the only option that matches the requested backward, every-other-character behavior.

Why this answer

The slicing syntax [::-2] means start from the end (default step negative), go to the beginning, and take every second character in reverse order. For 'abcdef', starting at 'f' (index -1), then skipping one to 'd' (index -3), then 'b' (index -5), resulting in 'fdb'. Option C is correct.

Exam trap

Candidates often mistakenly think that [::-2] starts from the beginning and skips every two characters forward, leading them to pick 'ace' (option B) instead of understanding that a negative step reverses the traversal order.

How to eliminate wrong answers

Option A is wrong because 'dfb' would require a step of -2 starting from index -2 ('e'), which is not what [::-2] does. Option B is wrong because 'ace' is the result of a positive step of 2 from the beginning (i.e., 'abcdef'[::2]), not a negative step. Option D is wrong because 'eca' would be the result of reversing the string and then taking every second character from the start (i.e., 'fedcba'[::2]), which is a different operation.

96
MCQeasy

A Python script uses a third-party library 'requests'. The developer wants to ensure that the exact version 2.25.1 is installed in the project's environment. Which tool and command should be used?

A.pip install requests
B.pip install requests==2.25.1
C.pip3 install requests==2.25.1
D.pip instll requests==2.25.1
AnswerB, C

This command correctly uses the pip package manager with an explicit version specifier. The == operator, an exact version pin defined by PEP 440, tells pip to install precisely requests 2.25.1 from PyPI, bypassing any newer or older release. This ensures reproducible dependency behavior across different machines and deployment stages.

Why this answer

Both options B and C are correct because they use the standard pip syntax for pinning a specific version: `package==version`. The command `pip install requests==2.25.1` (B) works on systems where `pip` is linked to Python 3, while `pip3 install requests==2.25.1` (C) explicitly invokes the Python 3 version of pip. Both achieve the same result—installing requests exactly version 2.25.1.

Option A fails to specify a version, and option D contains a typo ('instll') that will fail.

Exam trap

A common pitfall is assuming that `pip3` is incorrect or non-standard. In reality, both `pip` and `pip3` are valid commands for Python 3 environments; the key is the version-pinning syntax (`==`). The question tests whether the candidate recognizes the correct syntax for specifying an exact version, not the distinction between `pip` and `pip3`.

How to eliminate wrong answers

Option A is wrong because `pip install requests` installs the latest available version of the library, not the exact version 2.25.1, which fails the requirement for version pinning. Option C is wrong because `pip3` is simply an alias for `pip` on many systems (or a Python 3-specific variant) and does not change the version specification; the command is functionally identical to option B, but the question asks for the correct tool and command, and `pip` is the standard tool name. Option D is wrong because `pip instll` contains a typo ('instll' instead of 'install'), which would cause the command to fail with a 'command not found' error.

97
Drag & Dropmedium

Drag and drop the steps to create and activate a virtual environment in Python into the correct order.

Drag or tap steps into the slots.

Steps
Order
1Step 1
2Step 2
3Step 3
4Step 4

Why this order

Virtual environments isolate project dependencies. The typical workflow is install virtualenv, create environment, activate it, install packages, and deactivate when finished.

98
MCQmedium

During development, a programmer modifies a module that is already imported in the current Python session. To see the changes without restarting the interpreter, which function from the importlib module should be called?

A.reload()
B.reload_module()
C.importlib.reload()
D.importlib.import_module()
AnswerC

This is the correct way to reload a module in Python 3. It takes a module object (already imported) and re-executes its source code, updating the module's attributes in place. It is particularly useful during development to pick up changes without restarting the interpreter. Note that it returns the updated module object, and other references to the old module still point to the same object (since it mutates in place).

Why this answer

`importlib.reload()` is the official Python function to re-import a previously imported module, applying any changes made to its source code without restarting the interpreter. It is part of the `importlib` module and is the recommended way to reload modules in Python 3.

Exam trap

Python Institute often tests the distinction between the Python 2 built-in `reload()` and the Python 3 `importlib.reload()` syntax, and candidates mistakenly choose the bare `reload()` option without realizing it is no longer a built-in function.

How to eliminate wrong answers

Option A is wrong because `reload()` is not a standalone built-in function; in Python 2 it existed as a built-in, but in Python 3 it was moved to `importlib` and must be called as `importlib.reload()`. Option B is wrong because `reload_module()` is not a valid function in the `importlib` module; the correct function name is `reload()`. Option D is wrong because `importlib.import_module()` is used to import a module programmatically, not to reload an already imported module; it does not update the existing module object in memory.

99
MCQmedium

Refer to the exhibit. Which of the following Python code snippets would generate this error?

A.int('abc')
B.str(123)
C.print('abc')
D.float('abc')
AnswerA

int('abc') invokes Python's integer constructor on the non-numeric string 'abc'. Since the string does not contain a valid integer literal—even after stripping surrounding whitespace or handling an optional sign—the conversion fails, raising a ValueError with the exact message "invalid literal for int() with base 10: 'abc'". This is the precise exception and message that the exhibit/question targets, making this the correct snippet.

Why this answer

`int('abc')` attempts to convert the string `'abc'` to an integer, which is not a valid numeric literal. Python raises a `ValueError` with the message 'invalid literal for int() with base 10: 'abc''. This error occurs because the `int()` function expects a string that represents a valid integer in the specified base (default base 10), and 'abc' does not meet that criterion.

Exam trap

Python Institute often tests the exact wording of Python error messages, so the trap here is that candidates may confuse `ValueError` from `int()` with `ValueError` from `float()`, but the error message in the exhibit specifically cites 'invalid literal for int()', making only `int('abc')` the correct match.

How to eliminate wrong answers

Option B is wrong because `str(123)` successfully converts the integer 123 to the string '123', which is a valid operation and does not raise any error. Option C is wrong because `print('abc')` simply prints the string 'abc' to the standard output; it does not involve any type conversion that could raise a ValueError. Option D is wrong because `float('abc')` would also raise a ValueError, but the error message would be 'could not convert string to float: 'abc'', which is different from the specific error shown in the exhibit (which mentions 'invalid literal for int()').

The exhibit's error message explicitly references `int()` and base 10, so only `int('abc')` matches.

100
MCQhard

Which of the following correctly uses `__slots__` to restrict attribute creation to only `x` and `y`?

A.`class Foo: __slots__ = 'x'`
B.`class Foo: __slots__ = ('x')`
C.`class Foo: __slots__ = ['x', 'y']`
D.`class Foo: __slots__ = ('x', 'y')`
AnswerC, D

A list such as ['x', 'y'] is also a valid iterable, so Python will happily consume it and create exactly those two slots. The interpreter does not require an immutable type for __slots__; it simply iterates over the object to collect the attribute names. However, because the list remains mutable and is stored as a class attribute, a later append or rebind could change the set of allowed attributes, which is why tuples are the conventional, safer choice.

Why this answer

Options C and D are both correct because `__slots__` must be assigned an iterable of strings. A list `['x', 'y']` and a tuple `('x', 'y')` are both valid iterables that restrict attribute creation to exactly `x` and `y`. Option A uses a single string, which would restrict to individual characters `'x'`; Option B uses a string in parentheses without a trailing comma, which is also just a string.

Both A and B would produce unexpected behavior.

Exam trap

Python Institute often tests the misconception that a single string or a parenthesized string without a trailing comma is a valid iterable for `__slots__`, leading candidates to pick options that inadvertently restrict attributes to individual characters rather than the intended attribute names. Additionally, candidates may overlook that a list is also a valid iterable.

How to eliminate wrong answers

Option A is wrong because `__slots__ = 'x'` assigns a single string, which is iterable (yielding characters 'x'), but this restricts attributes to the single character 'x', not the intended attribute name `x`. Option B is wrong because `__slots__ = ('x')` is not a tuple — parentheses without a trailing comma create just the string `'x'`, which again iterates over characters. Option C is wrong because while `['x', 'y']` is a valid iterable and would work technically, the question asks for the correct use to restrict to `x` and `y`; however, the exam considers tuples as the canonical form for `__slots__`, and using a list is less common but not incorrect — but the question's correct answer is D as the most standard and unambiguous form.

101
MCQmedium

A developer needs to replace all occurrences of 'cat' with 'dog' in a string, but only if 'cat' is a whole word (not part of 'category'). Which code achieves this?

A.re.sub(r'\bcat\b', 'dog', s)
B.s.replace('cat', 'dog')
C.re.sub('cat', 'dog', s)
D.s.replace('cat', 'dog', 1)
AnswerA

The correct solution uses a raw-string regex pattern with word boundary anchors: `\b` at both ends ensures `cat` is only matched as a standalone word, not as a substring inside larger words like `category` or `bobcat`. `re.sub` then replaces every such whole-word occurrence globally, returning a new string with each standalone `cat` changed to `dog`, leaving all other text intact.

Why this answer

Uses the `re.sub()` function with the regex pattern `r'\bcat\b'`, where `\b` denotes a word boundary. This ensures that only the whole word 'cat' is matched and replaced with 'dog', ignoring cases where 'cat' appears as part of a larger word like 'category'. The `r` prefix makes it a raw string, preventing escape sequence issues.

Exam trap

Python Institute often tests the distinction between simple string methods and regex-based substitution, specifically the need for word boundary anchors (`\b`) to match whole words, which candidates overlook when they assume `replace()` or a plain `re.sub()` pattern is sufficient.

How to eliminate wrong answers

Option B is wrong because `s.replace('cat', 'dog')` performs a simple substring replacement, replacing every occurrence of 'cat' regardless of word boundaries, so 'category' would become 'dogegory'. Option C is wrong because `re.sub('cat', 'dog', s)` without word boundary anchors matches 'cat' anywhere in the string, including inside other words, leading to the same issue as Option B. Option D is wrong because `s.replace('cat', 'dog', 1)` replaces only the first occurrence of 'cat' (not all) and still does not respect word boundaries, so it fails both requirements.

102
MCQhard

A developer is working with a string `text = "The quick brown fox jumps over the lazy dog"`. They want to create a new string where every occurrence of the word 'the' (case-insensitive) is replaced with 'a'. However, they must not replace 'the' when it is part of another word, such as in 'then' or 'there'. Which approach correctly achieves this?

A.text.lower().replace('the', 'a')
B.import re; re.sub('the', 'a', text, flags=re.IGNORECASE)
C.import re; re.sub(r'\bthe\b', 'a', text, flags=re.IGNORECASE)
D.text.replace('the', 'a').replace('The', 'a')
AnswerC

The regular expression uses word boundaries (\b) to match 'the' only when it appears as a whole word. The re.IGNORECASE flag makes it case-insensitive, so 'The' and 'the' are both matched. This correctly replaces only the standalone word 'the' with 'a', leaving words like 'then' or 'there' unchanged. The result would be 'a quick brown fox jumps over a lazy dog' (with 'The' replaced by 'a' and 'the' replaced by 'a').

Why this answer

To replace a whole word case-insensitively, regular expressions with word boundaries are needed. The pattern \bthe\b matches 'the' only when it is a complete word, and the re.IGNORECASE flag makes it case-insensitive. The other methods either replace substrings within words, lose the original casing, or do not handle case variations properly.

The correct approach preserves the rest of the string and only substitutes standalone occurrences.

Exam trap

The trap here is assuming that simple string replace with case conversion or multiple replace calls can handle whole-word matching, when in fact word boundaries require regular expressions.

103
MCQeasy

A developer wants to convert a string 'Python' to all uppercase letters. Which string method should be used?

A.capitalize()
B.title()
C.swapcase()
D.upper()
AnswerD

The `str.upper()` method returns a new string with all alphabetic characters converted to uppercase, leaving non-alphabetic characters unchanged. For the string `'Python'`, it produces `'PYTHON'` without modifying the original string, satisfying the requirement for a non-destructive transformation. This method operates on each Unicode character’s case mapping, ensuring correct conversion for the given ASCII input.

Why this answer

The `upper()` method returns a copy of the string with all lowercase characters converted to uppercase. Since the goal is to convert 'Python' to 'PYTHON', `upper()` is the correct and most direct method for this task.

Exam trap

The Python PCAP exam often tests the distinction between `upper()` and `capitalize()` or `title()`, where candidates mistakenly choose `capitalize()` thinking it converts the entire string to uppercase, but it only capitalizes the first character.

How to eliminate wrong answers

Option A is wrong because `capitalize()` converts only the first character to uppercase and the rest to lowercase, resulting in 'Python' (no change) or 'python' if the string were all lowercase. Option B is wrong because `title()` capitalizes the first character of each word, which for a single word like 'Python' would produce 'Python' (no change) and is not designed for full uppercase conversion. Option C is wrong because `swapcase()` inverts the case of each character, turning 'Python' into 'pYTHON', not the desired all-uppercase result.

104
Multi-Selecteasy

Which TWO of the following are correct statements about the 'with' statement in Python file I/O?

Select 2 answers
A.It ensures the file is closed when the block exits.
B.It can only be used with file objects.
C.It automatically opens the file without needing open().
D.It ensures that resources are released even if an exception is raised.
E.It prevents any exception from occurring during file operations.
AnswersA, D

The `with` statement invokes the context manager's `__exit__` method on block exit, and for file objects that method calls `close()`. This makes file closure deterministic and automatic, even if the block ends early due to a `return`, `break`, or `continue`. A bare `open()` without `try/finally` would not provide this guarantee.

Why this answer

The 'with' statement in Python acts as a context manager that automatically calls the file object's __exit__ method when the block exits, which in turn invokes the file's close() method. This guarantees that the file is properly closed, even if an exception occurs within the block, ensuring deterministic resource cleanup.

Exam trap

Python Institute often tests the misconception that the 'with' statement is exclusive to file objects or that it automatically opens files, when in fact it is a general-purpose context manager that requires an already-opened resource and does not suppress exceptions.

105
MCQeasy

A team is using a shared Python environment where multiple projects have conflicting dependencies. Which approach is the best practice to isolate project dependencies?

A.Create a virtual environment using 'python -m venv' and install dependencies inside it.
B.Manually modify sys.path in each script to include different package directories.
C.Install all dependencies in the system-wide site-packages directory.
D.Install all packages using 'pip install --user' to avoid system conflicts.
AnswerA

Using 'python -m venv' creates an isolated environment with its own Python binary and site-packages directory, allowing each project to install exactly the dependencies and versions it needs without interfering with other projects. This is the standard, built-in best practice for managing project dependencies in a shared Python environment, and it also makes it easy to generate a reproducible requirements.txt for teammates.

Why this answer

Using `python -m venv` creates an isolated virtual environment with its own `site-packages` directory, preventing dependency conflicts between projects. This is the standard best practice recommended by the Python Packaging Authority (PyPA) for managing project-specific dependencies without affecting the system-wide Python installation.

Exam trap

The trap here is that candidates may think `pip install --user` provides isolation similar to a virtual environment, but it only separates user-level from system-level packages, not between projects, so it fails to solve the core problem of conflicting dependencies across multiple projects.

How to eliminate wrong answers

Option B is wrong because manually modifying `sys.path` in each script is fragile, error-prone, and does not isolate dependencies at the package level—it only alters the module search path, leaving the global environment unchanged and still susceptible to version conflicts. Option C is wrong because installing all dependencies in the system-wide `site-packages` directory directly causes the very conflicts the team is trying to avoid, as different projects may require different versions of the same package. Option D is wrong because `pip install --user` installs packages in the user-specific `site-packages` directory (e.g., `~/.local/lib/pythonX.Y/site-packages`), which is shared across all projects run by that user, so it does not provide per-project isolation and can still lead to dependency conflicts.

106
MCQeasy

A class defines a variable `count = 0`. An instance modifies `self.count = 5`. What is the value of `count` in the class namespace?

A.0, because the class variable remains unchanged.
B.5, because the instance modified the class variable.
C.0, but the assignment raises an AttributeError.
D.The class variable is deleted and the instance has 5.
AnswerA

The correct outcome is 0 because assigning to the instance attribute `count` does not touch the class-level `count`. In Python, an assignment like `obj.count = 5` writes a new entry into the instance's `__dict__`, and subsequent lookups find that instance attribute first, shadowing the class variable. The class namespace still holds `count = 0`, so it remains unchanged.

Why this answer

When an instance assigns `self.count = 5`, Python creates an instance attribute that shadows the class variable `count` in the instance's namespace. The class variable `count` remains unchanged at 0 in the class namespace, as instance attribute assignment does not modify class attributes.

Exam trap

The trap here is that candidates mistakenly believe `self.count = 5` modifies the class variable, but Python's assignment semantics always create or update an instance attribute, leaving the class variable untouched.

How to eliminate wrong answers

Option B is wrong because it assumes instance assignment modifies the class variable directly, but Python's attribute lookup creates a new instance attribute rather than altering the class-level `count`. Option C is wrong because no AttributeError is raised; assignment to `self.count` is perfectly valid and simply creates an instance attribute. Option D is wrong because the class variable is not deleted; it still exists in the class namespace and can be accessed via `ClassName.count`.

107
MCQeasy

A developer wants to create a string that contains the current year and month in the format 'YYYY-MM'. The year and month are stored in integer variables year and month. Which expression would produce the desired result?

A.f"{year}-{month:02d}"
B.year + '-' + month
C.str(year) + '-' + str(month)
D.'%s-%s' % (year, month)
AnswerA

The f-string `f"{year}-{month:02d}"` evaluates both expressions inline and applies a format specification to the month placeholder. The `02d` specifier instructs Python to format the integer month as a decimal with a minimum width of two characters, padding with a leading zero when needed. This produces the desired ISO-like format, e.g., `2024-03` for March.

Why this answer

Uses an f-string with the format specifier `:02d` to zero-pad the month to two digits, ensuring that months 1-9 appear as '01', '02', etc. This produces the exact 'YYYY-MM' format from integer variables without manual conversion or padding.

Exam trap

The PCAP exam often tests the distinction between string concatenation with `+` (which requires both operands to be strings) and f-string formatting, and the trap here is that candidates may forget to zero-pad the month, choosing Option C because it 'works' syntactically but fails the format requirement.

How to eliminate wrong answers

Option B is wrong because it attempts to concatenate integers directly with the `+` operator, which raises a `TypeError` in Python (cannot concatenate `int` and `str`). Option C is wrong because while it correctly converts both integers to strings, it does not zero-pad the month, so a month value of 3 would produce '2025-3' instead of '2025-03'. Option D is wrong because it uses old-style `%` formatting without a format specifier for zero-padding; `%s` simply converts the integer to a string without padding, yielding '2025-3' for month=3.

108
MCQeasy

What is the output of `print('-'.join(['a', 'b', 'c']))`?

A.a-b-c
B.['a', '-', 'b', '-', 'c']
C.('a', '-', 'b', '-', 'c')
D.a,b,c
AnswerA

The `str.join()` method returns a single string built by concatenating each element of the iterable with the given separator placed between them. Since the separator is '-' and the elements are 'a', 'b', and 'c', the correct printed output is the string `a-b-c`. The quotes are only part of the string literal in code, not part of the printed value, so the output has no brackets or extra characters.

Why this answer

The `join()` method in Python concatenates the elements of an iterable (here, a list of strings) into a single string, using the string on which it is called as the separator. In this case, `'-'.join(['a', 'b', 'c'])` inserts a hyphen between each element, producing the string `'a-b-c'`. The `print()` function then outputs that string without quotes.

Exam trap

Python Institute often tests whether candidates understand that `join()` returns a single string, not a list or tuple, and that the separator is placed between elements, not appended at the ends.

How to eliminate wrong answers

Option B is wrong because it shows a list `['a', '-', 'b', '-', 'c']`, which would be the result of incorrectly flattening the separator into the list rather than using `join()`. Option C is wrong because it shows a tuple `('a', '-', 'b', '-', 'c')`, which similarly misrepresents the output as a tuple of separate characters. Option D is wrong because `'a,b,c'` uses commas as separators, which would be produced by `','.join(['a', 'b', 'c'])`, not the hyphen separator specified in the question.

109
MCQhard

A cloud infrastructure engineer is developing a Python script to parse large configuration files from a fleet of servers. Each file can be up to 500 MB. The script reads the file line by line using a file object, strips comment lines (those starting with '#'), and accumulates only the configuration directives into a single string for further processing. The current code is: ```python result = '' with open('config.cfg') as f: for line in f: if not line.startswith('#'): result += line.strip() ``` After processing just a few hundred lines of a large file, the script becomes extremely slow and consumes an excessive amount of memory. The engineer identifies that string concatenation using `+=` is inefficient because strings are immutable, causing repeated memory reallocation. Which approach should the engineer implement to resolve the performance issue without changing the final output?

A.Replace `result += line.strip()` with `result = result + line.strip()`.
B.Use `io.StringIO` to write lines and then retrieve content with `.getvalue()`.
C.Use `str.join` called on the file object: `f.join('')`.
D.Use a list to collect stripped lines and then call `''.join(lines)` after the loop.
AnswerD

Store each stripped line as an element in a list during the loop; appending to a list is amortized O(1). After the loop, call `''.join(lines)` to allocate the final string exactly once and copy each part in a single pass, producing O(n) total work. This is the canonical idiom because it avoids repeated string reallocation and takes advantage of `str.join`'s optimized internal traversal of the sequence.

Why this answer

It avoids the O(n²) time complexity of repeated string concatenation by collecting stripped lines in a list and then joining them once with `''.join(lines)`. This leverages the efficient memory allocation of `str.join`, which precomputes the total size and allocates exactly once, solving the performance and memory issue without altering the final output.

Exam trap

Candidates often incorrectly believe that `result = result + line.strip()` is more efficient than `result += line.strip()`, but both have the same O(n^2) performance due to string immutability. The correct solution is to collect lines in a list and join them with `''.join()`.

How to eliminate wrong answers

Option A is wrong because `result = result + line.strip()` is semantically identical to `result += line.strip()` — both create a new string object and cause the same O(n²) reallocation overhead. Option B is wrong because `io.StringIO` is designed for in-memory text streams and would still require a final `.getvalue()` call, but it does not inherently solve the concatenation inefficiency; it adds unnecessary overhead for this simple accumulation task. Option C is wrong because `str.join` is a method on a string separator, not on a file object; `f.join('')` would raise an `AttributeError` since file objects have no `join` method.

110
MCQeasy

A developer writes a script to read a configuration file that may not exist. The script should handle the error gracefully and continue. Which approach is most Pythonic?

A.Use a try-except block catching FileNotFoundError
B.Use a try-except block catching OSError
C.Use os.path.exists to check, then open if it exists
D.Use an if statement to check file size
AnswerA

Catching FileNotFoundError explicitly handles the missing-file condition while allowing execution to continue, which is the idiomatic Python approach. It satisfies the graceful-continuation constraint precisely, unlike broad exception handling or pre-checking with os.path.exists, which introduces race conditions.

Why this answer

It directly catches the specific `FileNotFoundError` exception, which is a subclass of `OSError` and is raised when a file does not exist. This approach follows the Pythonic principle of EAFP (Easier to Ask for Forgiveness than Permission), allowing the script to attempt the operation and handle the failure gracefully without redundant checks.

Exam trap

Python Institute often tests the distinction between catching a specific exception (`FileNotFoundError`) versus a broader parent exception (`OSError`), and the trap here is that candidates may choose the broader catch thinking it is safer, without realizing it can mask other critical errors.

How to eliminate wrong answers

Option B is wrong because catching `OSError` is too broad; it would also catch other operating system errors (e.g., permission denied, disk full) that may require different handling, masking the specific file-not-found scenario. Option C is wrong because using `os.path.exists` introduces a race condition (TOCTOU — Time of Check to Time of Use) where the file could be deleted or created between the check and the open call, and it violates the Pythonic EAFP idiom by using LBYL (Look Before You Leap). Option D is wrong because checking file size does not determine if a file exists; a file with zero size exists, and a non-existent file has no size to check, making this approach logically incorrect and unreliable.

111
MCQeasy

A subclass overrides a method but wants to call the parent class's version. Which keyword should be used?

A.base
B.super()
C.parent
D.self
AnswerB

super() is the correct mechanism because it returns a proxy object that forwards method calls to the next class in the method resolution order (MRO). This allows the subclass's overridden method to invoke the parent implementation without naming the parent class explicitly, which remains correct even under multiple inheritance. A typical usage is super().method_name(args), though in Python 3 you can also call it without arguments inside a class method.

Why this answer

In Python, the `super()` function is used to call a method from the parent class. When a subclass overrides a method, `super().method_name()` allows the subclass to invoke the parent class's implementation, enabling cooperative multiple inheritance and proper method resolution order (MRO). This is the correct keyword for accessing the parent class's version of an overridden method.

Exam trap

Python Institute often tests the distinction between `super()` and `self`, where candidates mistakenly think `self` can access the parent's overridden method, but `self` always refers to the current instance and will call the subclass's version if overridden.

How to eliminate wrong answers

Option A is wrong because `base` is not a keyword in Python; it is used in C# for accessing base class members, but Python uses `super()`. Option C is wrong because `parent` is not a Python keyword or built-in function; it has no meaning in Python's object-oriented syntax. Option D is wrong because `self` refers to the current instance of the class, not the parent class; it cannot be used to call the parent class's overridden method directly.

112
MCQmedium

You are developing a package 'analytics' that contains subpackages 'stats' and 'ml'. The __init__.py of 'analytics' imports a function 'normalize' from 'analytics.stats'. When a user runs `import analytics`, they get an ImportError. Which change ensures the package imports correctly?

A.Change the import to: from stats import normalize
B.Add sys.path.append('.') before the import in __init__.py
C.Change the import in analytics/__init__.py to: from .stats import normalize
D.Move the import statement to the stats/__init__.py file
AnswerC

`from .stats import normalize` is a relative import: the leading dot tells CPython's import machinery to start from the current package, `analytics`, and resolve `.stats` as `analytics.stats` (PEP 328). This works regardless of the absolute `sys.path` layout, whether `analytics` is installed as a package or invoked from a script, and it avoids name collisions with any unrelated top-level `stats` module. Since the statement appears in `analytics/__init__.py`, `.stats` is unambiguous and correctly loads the sibling submodule, making `normalize` available as `analytics.normalize`.

Why this answer

It uses an explicit relative import (`from .stats import normalize`), which is the proper way to import from a subpackage within a package. Absolute imports like `from analytics.stats import normalize` can fail if the package's parent directory is not in `sys.path`, which is common when running scripts directly. Relative imports resolve correctly based on the package structure, ensuring the import works regardless of how the package is invoked.

Exam trap

Python Institute often tests the distinction between absolute and relative imports in packages, and the trap here is that candidates mistakenly think absolute imports like `from analytics.stats import normalize` are always safe, not realizing they depend on the package being installed or the parent directory being in `sys.path`.

How to eliminate wrong answers

Option A is wrong because `from stats import normalize` uses an absolute import without the package prefix, which will look for a top-level module named `stats` rather than the subpackage `analytics.stats`, causing a ModuleNotFoundError. Option B is wrong because `sys.path.append('.')` adds the current working directory to the module search path, which is unreliable and does not guarantee that the package's parent directory is in `sys.path`; it also violates best practices by modifying `sys.path` in `__init__.py`. Option D is wrong because moving the import to `stats/__init__.py` would not make `normalize` available at the `analytics` package level when a user runs `import analytics`; the import must be in `analytics/__init__.py` to be part of the package's namespace.

113
Matchingmedium

Match each Python operator to its precedence level (1=highest).

Drag a concept onto its matching description — or click a concept then click the description.

Concepts
Matches

1

3

4

7

8

Why these pairings

Operator precedence from highest to lowest: ** (exponent) > *, /, //, % (multiplicative) > +, - (additive).

114
MCQmedium

A team is developing a data processing pipeline where each step is a class that implements a common interface. They have defined an abstract base class DataProcessor with an abstract method process(data). Several concrete subclasses implement process. Now they need to add a new step that logs the data before processing. They want to reuse the existing processing logic without modifying the original classes. Which design pattern should they apply?

A.Factory pattern to instantiate processors dynamically.
B.Decorator pattern by creating a LoggingProcessor subclass that wraps another processor and calls its process method after logging.
C.Singleton pattern to ensure only one logger exists.
D.Observer pattern to notify loggers of data changes.
AnswerB

The Decorator pattern is correct because it lets you attach new responsibilities to an object without modifying its class. A LoggingProcessor subclass that holds a reference to another processor and invokes its process method after emitting log output is the canonical decorator implementation, preserving the processor interface while transparently adding logging. This wrapper can be applied to any processor instance at runtime and can even be stacked with other decorators.

Why this answer

The Decorator pattern allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. By creating a LoggingProcessor that wraps an existing DataProcessor and delegates to its process method after logging, the team reuses the original processing logic without modifying the existing classes, adhering to the Open/Closed Principle.

Exam trap

Python Institute often tests the Decorator pattern in scenarios where the requirement is to add responsibilities to objects dynamically without altering their structure, and the trap is that candidates confuse it with the Factory pattern because both involve creating objects, but the Decorator focuses on extending behavior, not on instantiation logic.

How to eliminate wrong answers

Option A is wrong because the Factory pattern is used to encapsulate object creation logic, not to add new behavior to existing objects; it would not help in adding logging without modifying the original classes. Option C is wrong because the Singleton pattern ensures a single instance of a class (e.g., a logger), but it does not provide a mechanism to wrap or extend the behavior of existing DataProcessor objects. Option D is wrong because the Observer pattern defines a one-to-many dependency for event notification, which is not suitable for wrapping a single processor to add logging before its execution.

115
MCQmedium

A company has a shared internal library stored in a Git repository. Developers need to use this library in multiple projects without copying the code. Which approach is the most Pythonic and maintainable?

A.Add the library's directory to sys.path in each project's main script.
B.Create a symbolic link to the library's directory inside each project.
C.Package the library as a proper Python package and install it via pip in each project's virtual environment.
D.Copy the library source code into each project's directory.
AnswerC

Packaging the library as a proper Python package and installing it with pip into each project's virtual environment is the industry-standard way to share internal code. The package gets its own version number and can be pinned to a tag or commit in git (for instance, pip install "mylib @ git+https://repo.git@v1.2.3"), with pip resolving its dependencies and installing it into an isolated, per-project site-packages area. This gives you reproducible builds, clean uninstall/upgrade paths, and consistent import behavior across every project.

Why this answer

Packaging the library as a proper Python package and installing it via pip in each project's virtual environment follows the Python packaging standard (PEP 517/518) and the principle of explicit dependency management. This approach ensures versioning, isolation, and easy updates without modifying sys.path or relying on fragile filesystem links, making it the most maintainable and Pythonic solution.

Exam trap

Python Institute often tests the misconception that modifying sys.path or using symbolic links is acceptable for sharing code, when in fact the Pythonic and maintainable solution is to package the library and install it via pip.

How to eliminate wrong answers

Option A is wrong because modifying sys.path at runtime is a fragile workaround that bypasses Python's import system and can cause namespace collisions or import order issues, especially in larger projects. Option B is wrong because symbolic links are platform-dependent, break easily when the repository is cloned or moved, and do not integrate with Python's packaging or dependency resolution tools. Option D is wrong because copying source code defeats the purpose of sharing a library, leads to code duplication, and makes updates impossible without manually synchronizing every project.

116
MCQhard

A developer is building a large string by concatenating many substrings in a loop using '+'. What is the main performance issue?

A.Each concatenation creates a new string object, leading to quadratic time complexity
B.String concatenation is not allowed in loops
C.Strings are immutable, so concatenation is impossible
D.The '+' operator works only for characters, not strings
AnswerA

Strings in Python are immutable, and the `+` operator cannot modify an existing string; it must allocate a brand-new string object and copy the contents of both operands into it. When you concatenate in a loop, each iteration copies all previously accumulated characters plus the new piece, so total work grows quadratically (O(n^2)) as the string length increases. Using a list and `str.join()` avoids this repeated copying.

Why this answer

In Python, strings are immutable, so the '+' operator does not modify an existing string but creates a new string object each time it is used. In a loop, this results in O(n²) time complexity because each concatenation copies the entire accumulated string, making it highly inefficient for large or many substrings.

Exam trap

Python Institute often tests the misconception that string immutability means concatenation is impossible or illegal, when in fact the real issue is the hidden performance cost of repeated object creation in loops.

How to eliminate wrong answers

Option B is wrong because string concatenation using '+' is syntactically allowed inside loops in Python; the issue is performance, not legality. Option C is wrong because while strings are immutable, concatenation is still possible—it creates a new string rather than modifying the original. Option D is wrong because the '+' operator is overloaded for strings and works perfectly for concatenating two or more strings, not just characters.

117
MCQeasy

A developer defines a class with an __init__ method that sets instance attributes. Which of the following is the correct way to call the parent class's __init__ from a child class?

A.super(self, Child).__init__(arg1, arg2)
B.Parent.__init__(self, arg1, arg2)
C.Child.__init__(self, arg1, arg2)
D.super().__init__(arg1, arg2)
AnswerD

super().__init__(arg1, arg2) is the idiomatic Python 3 way to invoke the parent initializer. The zero-argument super() call automatically captures the current class and instance via the compiler's __class__ cell, returning a proxy that resolves to the next class in the MRO. This preserves cooperative multiple inheritance, ensuring that each class in the hierarchy is initialized correctly and that diamond dependencies are handled safely.

Why this answer

`super().__init__(arg1, arg2)` is the modern, recommended way to call the parent class's `__init__` method in Python. It uses the `super()` function without arguments to automatically resolve the parent class based on the method resolution order (MRO), ensuring proper cooperative multiple inheritance and avoiding hardcoding the parent class name.

Exam trap

The PCAP exam often tests the misconception that `super()` requires explicit arguments or that calling the parent class directly by name (e.g., `Parent.__init__(self, ...)`) is the standard or recommended approach, when in fact `super().__init__(...)` is the Pythonic way and is required for proper MRO handling in complex hierarchies.

How to eliminate wrong answers

Option A is wrong because `super(self, Child).__init__(arg1, arg2)` incorrectly passes `self` as the first argument and `Child` as the second; the correct syntax is `super(Child, self).__init__(arg1, arg2)` or, more simply, `super().__init__(arg1, arg2)`. Option B is wrong because `Parent.__init__(self, arg1, arg2)` is an explicit call that bypasses the MRO and can break cooperative multiple inheritance, though it works in single inheritance; it is not the 'correct' way in modern Python. Option C is wrong because `Child.__init__(self, arg1, arg2)` would call the child class's own `__init__` method, leading to infinite recursion and a `RecursionError`.

118
Matchingmedium

Match each Python module to its purpose.

Drag a concept onto its matching description — or click a concept then click the description.

Concepts
Matches

Mathematical functions

Generate pseudo-random numbers

Manipulate dates and times

Work with JSON data

Interact with operating system

Why these pairings

Common Python standard library modules: os (operating system interface), sys (interpreter access), json (JSON handling), datetime (date and time), re (regular expressions), math (mathematical functions).

119
MCQhard

Consider the code fragment: f = open('data.txt', 'r') data = f.read() process_data(data) f.close() What is the primary risk if an exception occurs during process_data(data)?

A.The file descriptor may be leaked because the close() call is skipped.
B.The exception will be silently suppressed.
C.The file will be automatically closed by Python's garbage collector immediately.
D.The file contents will be corrupted.
AnswerA

An exception inside `process_data(data)` unwinds the stack before `f.close()` executes, leaving the file descriptor open until garbage collection reclaims it. That satisfies the stem's leak constraint: `close()` is skipped, so the descriptor is not released deterministically. A `with` statement or `try/finally` guarantees closure.

Why this answer

If `process_data(data)` raises an exception, the `f.close()` statement is never executed, leaving the file descriptor open. This is a resource leak that can exhaust system file handles, especially in long-running applications. Python's `with` statement is the recommended approach to guarantee automatic cleanup even when exceptions occur.

Exam trap

Python Institute often tests the misconception that Python's garbage collector immediately closes files, when in reality it only closes them during an unpredictable collection cycle, making explicit cleanup essential.

How to eliminate wrong answers

Option B is wrong because exceptions are not silently suppressed; they propagate up the call stack unless caught by an explicit `try-except` block. Option C is wrong because Python's garbage collector does not immediately close file descriptors; it may close them at an indeterminate time, and relying on it is poor practice and can lead to resource exhaustion. Option D is wrong because an exception during `process_data(data)` does not corrupt the file contents on disk; the file was opened in read mode, and the data is already read into memory before the exception occurs.

120
Multi-Selectmedium

Which TWO of the following are true about the 'with' statement in Python file I/O?

Select 2 answers
A.It can only be used with file objects
B.It is equivalent to a try/finally block
C.It automatically closes the file after the block
D.It does not require an __exit__ method
E.It requires the object to have an __enter__ method only
AnswersB, C

The with statement is semantically equivalent to a try/finally block because the interpreter guarantees that __exit__ is invoked when the block ends, whether it finishes normally or an exception is raised. This ensures cleanup code always runs, just as finally does after try. The key difference is that with also calls __enter__ at the start to initialize the resource, making it a structured way to pair setup and teardown.

Why this answer

The 'with' statement in Python is designed to simplify exception handling by encapsulating the setup and teardown of a resource in a context manager, which is functionally equivalent to a try/finally block. When you use 'with', the context manager's __exit__ method is guaranteed to be called even if an exception occurs, ensuring that cleanup actions like closing a file are performed, just as a finally clause would.

Exam trap

Python Institute often tests the misconception that the 'with' statement is only for file I/O, but the trap here is that candidates forget the context manager protocol requires both __enter__ and __exit__ methods, not just one, and that it applies to any object implementing that protocol.

121
Multi-Selectmedium

Which three statements about the Method Resolution Order (MRO) in Python are true? (Choose three.)

Select 3 answers
A.The MRO can be viewed using the __mro__ attribute.
B.MRO is determined by the C3 linearization algorithm.
C.The MRO is only used for methods, not attributes.
D.In diamond inheritance, the topmost base class is visited last.
E.The MRO can be changed by modifying the class hierarchy at runtime.
AnswersA, B, D

The __mro__ attribute exposes the resolved lookup order as a tuple of classes, letting you inspect exactly how Python will search for methods and attributes. It satisfies the scenario's need to view the MRO directly on a class.

Why this answer

Option A is correct because Python exposes the computed MRO as a tuple through the __mro__ attribute on every class (and also via ClassName.mro()), so it can be inspected directly. Option B is correct because since Python 2.3 the MRO is computed using the C3 linearization algorithm, which guarantees a consistent, monotonic ordering that respects local precedence and the order of base classes. Option D is correct because in a diamond hierarchy C3 linearization places the most derived class first and the common topmost base class (e.g., object) last, after all intermediate classes.

Option C is not correct because the MRO governs attribute lookup in general, including non-method attributes such as data descriptors and class variables, not just methods. Option E is not correct because the MRO is computed once when the class is created and is stored in the class's __mro__; altering the class hierarchy at runtime does not recompute or allow modification of an existing class's MRO.

Exam trap

Python Institute often tests the misconception that the MRO only applies to methods, when in fact it governs all attribute lookups, including data attributes and descriptors.

122
MCQeasy

A package named 'utilities' contains a submodule 'strings'. Which import statement allows the use of the function 'reverse' defined in utilities.strings as reverse() without needing to prefix it?

A.import utilities.strings
B.from utilities import strings
C.from utilities import *
D.from utilities.strings import reverse
AnswerD

This statement directly locates the `reverse` function inside the `utilities.strings` submodule and binds it under the name `reverse` in the current namespace. Unlike the other options, no package or submodule qualifier is needed; you can invoke `reverse()` immediately. It is the only option that gives you the function itself rather than a module or package reference.

Why this answer

It directly imports the `reverse` function from the `utilities.strings` submodule into the current namespace, allowing it to be called as `reverse()` without any prefix. This is the only option that imports the specific function rather than the module or package.

Exam trap

The trap here is that candidates often confuse importing a module or submodule with importing a specific attribute, leading them to pick options like A or B that still require a prefix, or option C which incorrectly assumes wildcard imports descend into submodules.

How to eliminate wrong answers

Option A is wrong because `import utilities.strings` imports the submodule, requiring the full qualified name `utilities.strings.reverse()` to call the function. Option B is wrong because `from utilities import strings` imports the `strings` submodule into the current namespace, so the function must be called as `strings.reverse()`. Option C is wrong because `from utilities import *` imports all names defined in the `utilities` package's `__init__.py`, not from its submodules; it does not import `reverse` from `utilities.strings` unless explicitly re-exported.

123
MCQmedium

A programmer writes a function to check if a string is a palindrome (ignoring case and non-alphanumeric characters). Which implementation correctly achieves this?

A.def is_pal(s): s = s.lower(); return s == ''.join(reversed(s))
B.def is_pal(s): s = ''.join(c for c in s if c.isalnum()).lower(); return s == s[::-1]
C.def is_pal(s): return s == s[::-1]
D.def is_pal(s): s = s.lower(); return s == s[::-1]
AnswerB

This is the correct palindrome checker because it first strips out every non-alphanumeric character with a generator expression and ''.join(), then lowercases the cleaned string. Comparing this normalized form to its extended-slice reverse s[::-1] accounts for spaces, punctuation, and mixed case in one clean pass. This is the robust, idiomatic approach expected for a general-purpose palindrome test.

Why this answer

It first filters the string to keep only alphanumeric characters using `c.isalnum()`, converts the result to lowercase with `.lower()`, and then compares the string to its reverse using slicing `s[::-1]`. This correctly handles case insensitivity and ignores non-alphanumeric characters, which is the standard approach for palindrome checking in Python.

Exam trap

Python Institute often tests the requirement to ignore non-alphanumeric characters and case, and the trap here is that candidates may forget to filter the string before reversing, leading them to choose options A or D which only handle case but not punctuation.

How to eliminate wrong answers

Option A is wrong because it only converts the string to lowercase but does not remove non-alphanumeric characters, so strings like 'A man, a plan, a canal: Panama' would fail. Option C is wrong because it performs a direct comparison without any case normalization or character filtering, so it would incorrectly reject palindromes with mixed case or punctuation. Option D is wrong because it converts to lowercase but does not strip non-alphanumeric characters, leading to false negatives for strings containing spaces or punctuation.

124
MCQeasy

A developer wants a class 'Point' to have a readable string representation that returns 'Point(x, y)'. Which special method should be overridden?

A.__repr__
B.__format__
C.__str__
D.__unicode__
AnswerA

__repr__ is the correct method because it defines the canonical, unambiguous string representation of an object, which is used by the repr() function and the interactive interpreter. Its goal is to return a string that, ideally, can be passed to eval() to recreate the object, making it the most developer-friendly and readable representation for diagnostic purposes. Since the developer wants a readable string for a class, __repr__ is the recommended choice to implement.

Why this answer

The `__repr__` method is designed to return an unambiguous string representation of an object, often used for debugging and development. Overriding `__repr__` to return `'Point(x, y)'` fulfills the requirement for a readable string representation that matches the specified format. This method is called by the `repr()` built-in function and by the interactive interpreter when evaluating an expression.

Exam trap

Python Institute often tests the distinction between `__repr__` and `__str__`, where candidates mistakenly choose `__str__` because they think 'readable' refers to user-friendly output, but the question's specific format 'Point(x, y)' is the classic `__repr__` pattern for unambiguous object representation.

How to eliminate wrong answers

Option B is wrong because `__format__` is used by the `format()` built-in function and f-strings to produce a formatted string based on a format specification, not to define a general readable representation. Option C is wrong because `__str__` returns an informal, user-friendly string representation (used by `print()` and `str()`), but the question specifically asks for a 'readable string representation' that returns 'Point(x, y)', which is the canonical format for `__repr__`; while `__str__` could be used, the standard practice and the question's phrasing point to `__repr__` as the correct special method for an unambiguous representation. Option D is wrong because `__unicode__` is a Python 2 method for returning a Unicode string; in Python 3, all strings are Unicode, and `__str__` serves that purpose, making `__unicode__` irrelevant in modern Python (and not part of the PCAP exam scope).

125
MCQhard

A developer writes a class 'Logger' with a class method 'log(msg)' that writes to a file. Another class 'AppLogger' inherits from 'Logger'. The developer expects both classes to share the same file handle. However, after creating an instance of 'AppLogger', the file handle is different. What is the most likely cause?

A.The 'log' method is defined as a class method using @classmethod
B.The file handle is opened in the __init__ method of the base class
C.The file handle is stored as a private attribute __file
D.The subclass overrides the 'log' method
AnswerB

Opening the file handle inside __init__ assigns the result to an instance attribute (via self), so each time a new Logger or subclass object is created, a separate descriptor is opened and stored on that specific instance. Because the handle is not attached to the class object, no sharing occurs between instances. This directly contradicts the premise that a single logger's file handle is shared, making this the correct flaw in the developer's code.

Why this answer

If the file handle is opened in the `__init__` method of the base class, each time a new instance is created (including when an `AppLogger` instance is created), a new file handle is opened. This means the `Logger` class and the `AppLogger` class do not share the same file handle; instead, each instance gets its own handle. To share a single file handle across all instances, the file handle should be opened as a class attribute or in a class method, not in `__init__`.

Exam trap

The trap here is that candidates often confuse instance attributes with class attributes, assuming that inheritance automatically shares instance-level resources, when in fact each instance gets its own copy of attributes defined in `__init__`.

How to eliminate wrong answers

Option A is wrong because using `@classmethod` for the `log` method does not cause different file handles; it simply means the method receives the class as the first argument, not the instance. The file handle sharing issue is about where the handle is opened, not the method decorator. Option C is wrong because storing the file handle as a private attribute `__file` (name mangling) does not inherently cause different handles; it only affects attribute access from subclasses.

The core issue remains that the handle is opened per instance in `__init__`. Option D is wrong because overriding the `log` method in the subclass would change the behavior of logging, but it would not cause the file handle to be different unless the override itself opens a new handle. The question states the developer expects both classes to share the same handle, and the problem is that after creating an instance of `AppLogger`, the handle is different—this points to the handle being created per instance, not to an override.

126
MCQmedium

An engineer is debugging an application that uses inheritance. The base class 'Vehicle' defines a method 'start()' that prints 'Vehicle started'. The subclass 'Car' overrides 'start()' to print 'Car started'. The code contains a function that accepts a Vehicle object and calls 'start()'. What is the output if a Car object is passed?

A.'Car started Vehicle started'
B.'Vehicle started'
C.'Car started'
D.AttributeError
AnswerC

Python's method resolution order searches the class hierarchy from the most derived class to the base, so for a Car instance, Car.start() is located first. That overridden method executes and prints exactly 'Car started'. The base implementation is overridden and not called unless the derived method explicitly delegates to it via super(), which it does not do here.

Why this answer

When a Car object is passed to a function expecting a Vehicle reference, Python uses dynamic dispatch (late binding) to call the overridden `start()` method defined in the Car class. Since the actual runtime type is Car, the overridden version executes, printing 'Car started'. This is a fundamental principle of polymorphism in Python.

Exam trap

Python Institute often tests the misconception that the declared parameter type (Vehicle) determines which method runs, leading candidates to pick 'Vehicle started', when in fact Python always uses the actual object's type at runtime.

How to eliminate wrong answers

Option A is wrong because it suggests both the base and subclass methods execute, which would require explicit super() calls or chained execution not present in the code. Option B is wrong because it assumes static binding based on the parameter type, ignoring Python's runtime method resolution. Option D is wrong because no AttributeError occurs; Car inherits from Vehicle and correctly overrides start(), so the method exists and is callable.

127
MCQeasy

In Python, if you have a try block followed by an except clause that catches all exceptions, which of the following is true about the else clause?

A.The else clause runs only if no exception is raised in the try block.
B.The else clause runs only if an exception occurs.
C.The else clause runs before the finally block regardless of exceptions.
D.The else clause is used to specify additional exception handlers.
AnswerA

The else clause is part of a try/except/else/finally compound statement. It executes only when the try block completes without raising any exception, meaning control flows to else immediately after the last statement in try. If an exception occurs, else is skipped and control jumps to a matching except handler instead. This makes else ideal for code that should run only on success, such as logging successful completion or proceeding with a computed result.

Why this answer

In Python, the `else` clause in a `try` statement executes only if no exception was raised in the `try` block. This is true regardless of whether the `except` clause catches all exceptions (e.g., bare `except:` or `except Exception:`). The `else` block is specifically designed for code that should run only when the `try` block completes successfully without any exception.

Exam trap

The trap here is that candidates often confuse the `else` clause with a second chance to handle exceptions or think it runs unconditionally before `finally`, when in fact it is strictly tied to the successful execution of the `try` block and is skipped entirely if any exception occurs.

How to eliminate wrong answers

Option B is wrong because the `else` clause runs only when no exception occurs, not when an exception occurs; code that runs on an exception belongs in the `except` block. Option C is wrong because the `else` clause runs before the `finally` block only if no exception was raised, but if an exception is raised, the `else` block is skipped entirely and the `finally` block still runs; the order is not guaranteed to be `else` before `finally` in all cases. Option D is wrong because the `else` clause is not used to specify additional exception handlers; additional exception handlers are specified by additional `except` clauses, while the `else` clause is for code that executes only on successful completion of the `try` block.

128
MCQmedium

What is the output of the code?

A.20
B.AttributeError: 'Derived' object has no attribute 'get_x'
C.10
D.AttributeError: 'Derived' object has no attribute '_x'
AnswerA

The correct output is 20 because Derived defines its own __init__ method that executes instead of Base.__init__. Inside Derived.__init__, the statement self._x = 20 creates an instance attribute _x bound to the integer 20. When get_x is called, Python's method resolution order finds get_x on Base, and that method returns self._x, which resolves to the Derived instance's attribute, giving 20.

Why this answer

The `Derived` class inherits the `get_x` method from the `Base` class, which returns `self._x`. When `obj.get_x()` is called, `self` refers to the `Derived` instance, and `self._x` accesses the `_x` attribute set in `Derived.__init__` (value 20). The `_x` attribute in `Derived` shadows the one in `Base`, so the output is 20.

Exam trap

Python Institute often tests the distinction between attribute shadowing and method inheritance, specifically that a derived class can override an attribute without calling the base class constructor, leading to unexpected values when inherited methods access that attribute.

How to eliminate wrong answers

Option B is wrong because `Derived` inherits `get_x` from `Base`, so the object does have that method; no AttributeError occurs. Option C is wrong because the `Derived` constructor sets `self._x = 20`, overriding the `_x = 10` set in `Base.__init__` (since `Derived.__init__` is called and does not call `super().__init__()`), so the value returned is 20, not 10. Option D is wrong because `_x` is a regular attribute (not a private name-mangled attribute), so it is accessible directly; no AttributeError occurs for `_x`.

129
MCQeasy

A developer creates a package named 'mypkg' with an __init__.py file. Inside the package, there is a module 'utils.py'. Which of the following is the correct way to import the function 'helper' from 'utils' from outside the package?

A.import mypkg.utils.helper
B.import mypkg; mypkg.utils.helper
C.from mypkg.utils import helper
D.from mypkg import utils.helper
AnswerC

This is the correct and idiomatic form because it explicitly names the submodule `utils` and the object `helper` in the `from ... import ...` syntax. The statement `from mypkg.utils import helper` tells Python to load `mypkg/utils` (the submodule) and then extract the attribute `helper` from that module's namespace, binding it locally as `helper`. This is the standard approach for importing a function or variable from a submodule directly, avoiding the need for dotted attribute chains.

Why this answer

It uses the standard Python syntax for importing a specific name from a submodule within a package: `from package.module import name`. This directly imports the `helper` function into the current namespace, making it callable without any prefix. The `__init__.py` file marks `mypkg` as a package, and `utils.py` is a module inside it, so `from mypkg.utils import helper` is the proper way to access `helper` from outside the package.

Exam trap

Python Institute often tests the distinction between importing a module versus importing an attribute from a module, and the trap here is that candidates confuse the `import` statement (which only accepts modules/packages) with the `from ... import` statement (which can import any object), leading them to choose Option A or D.

How to eliminate wrong answers

Option A is wrong because `import mypkg.utils.helper` attempts to import a module named `helper`, but `helper` is a function, not a module; Python's import system only supports importing modules or packages, not individual objects like functions or classes, via the `import` statement. Option B is wrong because `mypkg.utils.helper` is not a valid attribute access after `import mypkg`; `import mypkg` only imports the top-level package, and to access `utils` you would need to import `mypkg.utils` explicitly (e.g., `import mypkg.utils`), otherwise `mypkg.utils` is undefined. Option D is wrong because `from mypkg import utils.helper` uses dot notation in the import name, which is invalid syntax; the `from ... import` statement expects a single module or a comma-separated list of names, not a dotted path to an attribute.

130
MCQmedium

A programmer uses a class method to create an alternative constructor for a `Point` class. The method should parse a string like "10,20" and return a `Point` instance with x=10, y=20. Which code snippet correctly implements this?

A.`def from_string(self, s):\n parts = s.split(',')\n return Point(int(parts[0]), int(parts[1]))`
B.`@staticmethod\ndef from_string(s):\n parts = s.split(',')\n return Point(int(parts[0]), int(parts[1]))`
C.`def from_string(cls, s):\n parts = s.split(',')\n return cls(int(parts[0]), int(parts[1]))`
D.`@classmethod\ndef from_string(cls, s):\n parts = s.split(',')\n return cls(int(parts[0]), int(parts[1]))`
AnswerD

This is the canonical alternative constructor pattern: the @classmethod decorator makes Python bind the actual class object to the cls parameter, so calling Point.from_string(...) passes Point as cls. Using cls(parts[0], parts[1]) instead of Point(...) means the method respects inheritance — a subclass that inherits from_string will construct instances of that subclass, not the base class. This is exactly how standard library methods such as datetime.fromtimestamp and dict.fromkeys work.

Why this answer

It uses the `@classmethod` decorator, which automatically passes the class (`cls`) as the first argument. This allows the method to create an instance of the class using `cls(...)`, making it a proper alternative constructor that works correctly even if the class is subclassed. The method parses the string "10,20" by splitting on the comma and converting the parts to integers.

Exam trap

The PCAP exam often tests the distinction between `@classmethod` and `@staticmethod` by presenting a method that looks like it should be a static method but actually needs access to the class for proper inheritance, tempting candidates to choose the static version or a plain method without a decorator.

How to eliminate wrong answers

Option A is wrong because it defines a regular instance method with `self` as the first parameter, but it is called on the class (not an instance), so `self` would receive the string argument, causing a TypeError or incorrect behavior. Option B is wrong because it uses `@staticmethod`, which does not receive the class as an argument; it hardcodes `Point` instead of using `cls`, so it does not support inheritance properly and is not a true alternative constructor. Option C is wrong because it lacks a decorator, so Python treats it as a regular instance method; the first parameter `cls` would be interpreted as `self`, leading to a mismatch when called on the class.

131
Matchingmedium

Match each exception to its cause.

Drag a concept onto its matching description — or click a concept then click the description.

Concepts
Matches

Operation on incompatible type

Function receives argument with correct type but invalid value

Sequence subscript out of range

Mapping key not found

Attribute reference or assignment fails

Why these pairings

Correct matches: ValueError with inappropriate value, TypeError with wrong type, IndexError with out-of-range index, KeyError with missing key. Common confusions arise from swapping the definitions of ValueError and TypeError, or TypeError and KeyError.

132
MCQmedium

A developer is cleaning data from a CSV file. They have a string `record = "John,Doe,35,Engineer"` and want to replace all commas with semicolons. However, they also want to ensure that any leading or trailing whitespace in the entire string is removed before the replacement. Which expression produces the desired result?

A.record.strip(',').replace(',', ';')
B.record.replace(',', ';').strip()
C.record.strip().replace(',', ';')
D.record.replace(' ', '').replace(',', ';')
AnswerC

The expression first calls .strip() on the record, removing any leading and trailing whitespace. Then .replace(',', ';') replaces every comma with a semicolon. For the given string without extra whitespace, it yields 'John;Doe;35;Engineer'. If there were leading or trailing spaces, they would be removed before replacement. This meets both requirements in the correct order.

Why this answer

The correct approach is to strip leading and trailing whitespace first, then replace commas with semicolons. Using strip() before replace ensures that any surrounding whitespace is removed without affecting internal spaces. The other options either remove the wrong characters, remove all spaces, or perform the operations in a different order that might not align with the stated requirement, though in this specific case some yield the same output.

However, the requirement explicitly states 'before the replacement', making the strip-then-replace order the intended one.

Exam trap

The trap here is assuming that strip() removes commas or that replacing spaces globally is equivalent to stripping only the edges.

133
MCQmedium

A developer wants to remove all leading and trailing whitespace from a string, but preserve internal spaces. Which line of code accomplishes this?

A.s = s.lstrip()
B.s = s.strip().lstrip()
C.s = s.replace(' ', '')
D.s = s.strip()
AnswerD

The strip() method returns a copy of the string with all leading and trailing whitespace removed, using the standard set of whitespace characters including space, tab, and newline, while leaving internal spaces intact. This precisely satisfies the requirement, making it the correct and idiomatic way to trim a string in Python.

Why this answer

`s.strip()` removes all leading and trailing whitespace characters (spaces, tabs, newlines) from the string while preserving internal spaces. This is the exact requirement: eliminate whitespace at the boundaries only, leaving the internal content unchanged.

Exam trap

Python Institute often tests the distinction between `strip()`, `lstrip()`, and `rstrip()`, and the trap here is that candidates may confuse `strip()` with `replace(' ', '')` or think that `lstrip()` alone is sufficient, failing to recognize that `strip()` handles both ends in one call.

How to eliminate wrong answers

Option A is wrong because `s.lstrip()` only removes leading whitespace, leaving trailing whitespace intact. Option B is wrong because `s.strip().lstrip()` is redundant — `strip()` already removes both leading and trailing whitespace, so calling `lstrip()` afterward does nothing extra and is unnecessary. Option C is wrong because `s.replace(' ', '')` removes all spaces in the string, including internal ones, which destroys the internal spacing the developer wants to preserve.

134
Multi-Selectmedium

Which TWO methods are valid ways to import a function named 'foo' from a module 'bar' that is part of a package 'pkg'?

Select 2 answers
A.import bar
B.from pkg.bar import foo
C.import pkg; pkg.bar.foo
D.from pkg import bar; bar.foo
E.import pkg.bar.foo
AnswersB, D

This is the canonical absolute import: the full module path `pkg.bar` is resolved, the submodule is loaded, and the name `foo` (the function object) is bound directly into the current namespace. After execution, you can call `foo()` without any package or module qualifier. It is explicit, unambiguous, and does not depend on any earlier side-effect imports.

Why this answer

The 'from pkg.bar import foo' syntax directly imports the 'foo' function from the 'bar' submodule within the 'pkg' package, making 'foo' available in the current namespace. Option D is also correct because it first imports the 'bar' module from 'pkg' using 'from pkg import bar', then accesses 'foo' as an attribute of 'bar' (bar.foo), which is a valid two-step approach.

Exam trap

Python Institute often tests the distinction between importing a module versus importing an attribute from a module, and the trap here is that candidates mistakenly think 'import pkg.bar.foo' (Option E) is valid syntax, when in fact you can only import modules or packages with the dotted-path import statement, not functions or classes.

135
MCQhard

Refer to the exhibit. If both data.txt and backup.txt do not exist, what is the output?

A.Prints contents of backup.txt
B.Nothing
C.FileNotFoundError
D.'No file found'
AnswerD

Because neither data.txt nor backup.txt exists, the outer try raises FileNotFoundError, and then the inner try also raises FileNotFoundError. The inner except FileNotFoundError block catches this second exception and executes print('No file found'), making that literal string the exact output displayed by the program.

Why this answer

The code uses a try-except block to catch FileNotFoundError when attempting to open 'data.txt'. In the except block, it prints 'No file found' and then attempts to open 'backup.txt'. Since both files do not exist, the except block executes and prints 'No file found' before the second open attempt raises another FileNotFoundError, which is unhandled and terminates the program.

However, the question asks for the output, which is the print statement executed before the error.

Exam trap

Python Institute often tests the distinction between output produced before an unhandled exception and the exception itself, tricking candidates into thinking the program crashes without any output.

How to eliminate wrong answers

Option A is wrong because backup.txt does not exist, so its contents cannot be printed; the code would raise a FileNotFoundError on the second open attempt. Option B is wrong because the except block explicitly prints 'No file found' before the second open attempt, so something is output. Option C is wrong because while a FileNotFoundError does occur on the second open, the question asks for the output, not the exception; the print statement executes first, producing output.

136
Multi-Selecthard

Given s = 'Python', which THREE of the following expressions evaluate to True? (Choose three.)

Select 3 answers
A.'th' in s
B.s[0] == 'p'
C.s.isupper()
D.s.isalpha()
E.s.istitle()
AnswersA, D, E

The `in` operator performs a substring membership test, returning `True` when the literal characters `'th'` appear consecutively anywhere within `s`. In `'Python'`, the substring `'th'` is found at indices 2–3 (the third and fourth characters), so this expression evaluates to `True`. Note that substring matching is case-sensitive and does not require word boundaries.

Why this answer

The `in` operator checks for substring membership, and 'th' is indeed a contiguous substring within the string 'Python'. Python's `in` operator performs a linear scan of the string to determine if the substring exists, returning True if found.

Exam trap

The PCAP exam often tests the case-sensitivity of string methods and operators, trapping candidates who forget that `in`, indexing, and comparison methods like `isupper()` are case-sensitive and that `istitle()` requires the first letter of each word to be uppercase and all subsequent letters lowercase.

137
MCQeasy

A beginner programmer writes: name = "Alice"; print("Hello " + name). Which string method alternative is more efficient and recommended for Python 3?

A.print("Hello {}".format(name))
B.print("Hello %s" % name)
C.print(f"Hello {name}")
D.print("Hello " + name) is fine
AnswerC

An f-string (formatted string literal) is the recommended and most idiomatic way to embed `name` directly into the string. The expression inside `{}` is evaluated at runtime using the current scope, so `f"Hello {name}"` precisely reads as 'Hello, followed by the value of `name`'. This approach is concise, readable, and avoids the overhead of a separate method call or operator, making it both efficient and Pythonic. Since Python 3.6, f-strings are the preferred method for most string formatting tasks.

Why this answer

F-strings (formatted string literals) are the most efficient and readable string formatting method introduced in Python 3.6. They evaluate expressions at runtime and directly interpolate variables into the string, avoiding the overhead of method calls or the older %-formatting, making them both faster and more Pythonic.

Exam trap

Python Institute often tests the distinction between older formatting methods (%-formatting and str.format()) and the modern f-string syntax, trapping candidates who think any valid method is equally recommended, when in fact f-strings are the preferred and most efficient choice in Python 3.6+.

How to eliminate wrong answers

Option A is wrong because str.format() is less efficient than f-strings due to the overhead of a method call and additional parsing, and it is not the recommended approach for simple variable interpolation in modern Python 3. Option B is wrong because the %-formatting style is the legacy C-style printf approach, which is less readable, less flexible, and deprecated in favor of f-strings and str.format(). Option D is wrong because simple concatenation with + creates multiple intermediate string objects and is less efficient and less readable than f-strings, especially when combining multiple variables or expressions.

138
MCQeasy

A developer is building a simulation of different types of vehicles. They have a base class Vehicle with an attribute speed initialized in __init__. They also have a subclass Car that inherits from Vehicle and adds an attribute fuel_type. The developer wants to ensure that every time a Car object is created, it also initializes the speed attribute from the Vehicle class. Which approach should the developer use?

A.Override the __new__ method of Vehicle.
B.In Car.__init__, call Vehicle.__init__(self) manually.
C.Use the @staticmethod decorator for initialization.
D.In Car.__init__, define speed directly without calling parent's __init__.
AnswerB

Once Car defines its own __init__, the inherited Vehicle.__init__ is no longer called by Python, so any attributes set there, such as speed, would be missing. Manually invoking Vehicle.__init__(self) inside Car.__init__ runs the parent's initialization code on the same Car instance, ensuring those attributes exist before Car adds its own. This direct call is valid, although super().__init__() is often preferred because it respects the class's method resolution order.

Why this answer

In Python, when a subclass overrides __init__, the parent class's __init__ is not automatically called. To ensure the speed attribute from Vehicle is initialized, the developer must explicitly call Vehicle.__init__(self) inside Car.__init__. This is a fundamental requirement of Python's inheritance mechanism for proper initialization of inherited attributes.

Exam trap

Python Institute often tests the misconception that subclass __init__ automatically calls the parent __init__, leading candidates to incorrectly assume no explicit call is needed or to choose a wrong option like D.

How to eliminate wrong answers

Option A is wrong because overriding __new__ is used for controlling object creation (e.g., singletons) and is not the standard way to initialize inherited instance attributes; __init__ is the correct place for initialization. Option C is wrong because the @staticmethod decorator defines a method that does not receive self or cls, and cannot be used to initialize instance attributes like speed or fuel_type; it is unrelated to inheritance initialization. Option D is wrong because defining speed directly in Car.__init__ without calling the parent's __init__ duplicates logic and breaks the principle of code reuse; it also risks missing any additional initialization that Vehicle.__init__ might perform in the future.

139
MCQmedium

A developer is designing a system where a `Car` class needs to reuse functionality from `Engine` and `Transmission` without creating a deep hierarchy. Which OOP principle should be applied?

A.Aggregation, where Car is part of Engine.
B.Singleton pattern for Engine.
C.Multiple inheritance to inherit from both.
D.Composition over inheritance.
AnswerD

Composition over inheritance correctly models the real-world relationship: a Car has-an Engine and has-a Transmission. By storing these as separate member objects (often passed via constructor or setter), Car delegates behavior such as start(), shift(), and stop() to its components. This allows the engine or transmission to be replaced or mocked independently, promoting loose coupling, testability, and adherence to the single-responsibility principle.

Why this answer

'Composition over inheritance,' is correct because it advocates building the Car class by composing it with Engine and Transmission objects (has-a relationships) rather than inheriting from them. This avoids a deep class hierarchy and provides flexibility to change or swap components at runtime, which is a key design principle in Python and OOP.

Exam trap

Python Institute often tests the distinction between 'is-a' (inheritance) and 'has-a' (composition) relationships, and the trap here is that candidates mistakenly choose multiple inheritance (Option C) because they think reusing functionality requires inheritance, ignoring the complexity and the explicit instruction to avoid a deep hierarchy.

How to eliminate wrong answers

Option A is wrong because Aggregation defines a 'has-a' relationship where the part (Engine) can exist independently of the whole (Car), but the statement 'Car is part of Engine' reverses the relationship and is semantically incorrect. Option B is wrong because the Singleton pattern ensures only one instance of a class, which is irrelevant to reusing functionality from Engine and Transmission; it solves a different problem (global state control). Option C is wrong because multiple inheritance can lead to the diamond problem and increased complexity, and the question explicitly wants to avoid a deep hierarchy; composition is the recommended alternative.

140
MCQmedium

A developer needs to parse a log file where each line contains a timestamp followed by a message. The timestamp format is 'YYYY-MM-DD HH:MM:SS'. Which string method is most appropriate to split the timestamp from the message?

A.str.rsplit()
B.str.splitlines()
C.str.partition()
D.str.split()
AnswerD

str.split() with no arguments splits on any run of whitespace, trimming leading and trailing spaces, and returns a list of non-empty substrings. For a log line like '2025-04-10 14:22:31 INFO message here', the timestamp (which contains no spaces) becomes the first element while the rest of the line is broken into subsequent elements, cleanly isolating the timestamp. It is the most direct method because it handles variable amounts of whitespace without requiring a separator to be specified.

Why this answer

Str.split(), is the most appropriate because it splits a string on whitespace by default. Although the timestamp 'YYYY-MM-DD HH:MM:SS' contains a space, using split() without arguments returns a list of all space-separated elements. Since the timestamp is always the first two elements (date and time), the developer can join them with a space to get the full timestamp.

Alternatively, split() can be used with a specified separator and maxsplit to achieve the desired split. This flexibility makes str.split() the best choice among the given options.

Exam trap

Python Institute often tests the distinction between str.split() and str.partition(), where candidates mistakenly choose str.partition() because they think it splits on the first space, but fail to realize that the timestamp itself contains a space, causing an incorrect split.

How to eliminate wrong answers

Option A is wrong because str.rsplit() splits from the right side of the string, which would incorrectly separate the last word of the message rather than the first space after the timestamp. Option B is wrong because str.splitlines() splits on line boundaries (newline characters), not on whitespace within a single line, so it cannot separate the timestamp from the message on the same line. Option C is wrong because str.partition() splits on the first occurrence of a specific separator string, but the timestamp contains spaces (between date and time), so using a space as the separator would split the timestamp itself, not separate it from the message.

141
MCQeasy

A developer creates a Python class with a method that is intended to be overridden in subclasses. Which approach best ensures that the method is not accidentally called on the base class?

A.Use 'pass' as the method body
B.Delete the method from the base class using 'del'
C.Add a comment '# override in subclass' inside the method body
D.Raise NotImplementedError inside the method body
AnswerD

Raising NotImplementedError in the base method body makes any direct call fail immediately at runtime, forcing subclasses to provide their own implementation. This satisfies the stem's requirement that the method is not accidentally invoked on the base class.

Why this answer

Raising NotImplementedError inside the base class method is the standard Python idiom for defining an abstract-like method that must be overridden in subclasses. If a subclass fails to override the method and it is called, Python will raise an explicit error at runtime, preventing accidental use of the base implementation. This approach enforces the contract that the method is intended only for subclasses, without requiring the `abc` module.

Exam trap

Python Institute often tests the distinction between documentation-based approaches (comments) and runtime enforcement (exceptions), leading candidates to mistakenly choose a comment or 'pass' as sufficient for preventing accidental base class usage.

How to eliminate wrong answers

Option A is wrong because using 'pass' as the method body creates a no-op method that silently does nothing when called on the base class, which defeats the purpose of preventing accidental invocation. Option B is wrong because deleting the method from the base class with 'del' would cause an AttributeError when the method is called on a base class instance, but it also prevents subclasses from inheriting and overriding the method, breaking the intended design. Option C is wrong because adding a comment '# override in subclass' inside the method body has no runtime effect; it is merely a documentation hint that does not enforce or prevent any behavior.

142
MCQhard

A developer wants a class 'LoggedDict' that behaves like a dict but logs all attribute access in the console. Which method override correctly implements this for getting an attribute?

A.def __get__(self, instance, owner): print(f'Access'); return self
B.def __getattribute__(self, name): print(f'Access {name}'); return super().__getattribute__(name)
C.def __getattr__(self, name): print(f'Access {name}'); return self.__dict__[name]
D.def __getitem__(self, key): print(f'Access {key}'); return dict.__getitem__(self, key)
AnswerB

This correctly overrides __getattribute__, which Python invokes for every normal attribute access using dot notation on an instance. It prints the attribute name, then delegates to super().__getattribute__(name) to perform the real lookup—this call to the parent implementation is essential both to return the actual attribute value and to avoid infinite recursion. In a loggeddict subclass, this will log access to keys like obj.name, obj.method, and inherited attributes, though implicit special-method calls may bypass it.

Why this answer

`__getattribute__` is the universal method called for every attribute access on an object. By overriding it, the developer can log the attribute name before delegating to the superclass implementation via `super().__getattribute__(name)`, which preserves the normal attribute lookup chain. This ensures that all attribute accesses (including those that exist and those that don't) are logged, which is the requirement for 'LoggedDict'.

Exam trap

Python Institute often tests the distinction between `__getattribute__` (called for every attribute access) and `__getattr__` (called only as a fallback when the attribute is not found), leading candidates to mistakenly choose `__getattr__` because it seems simpler or because they confuse it with the general 'get attribute' concept.

How to eliminate wrong answers

Option A is wrong because `__get__` is the descriptor protocol method, invoked when an attribute is accessed on a class that owns a descriptor instance, not for general attribute access on a dict-like object. Option C is wrong because `__getattr__` is only called when normal attribute lookup fails (i.e., when `__getattribute__` raises an AttributeError), so it would not log successful accesses; additionally, using `self.__dict__[name]` bypasses the dict's own storage and can cause infinite recursion or missing keys. Option D is wrong because `__getitem__` is used for subscription access (e.g., `obj[key]`), not for attribute access (e.g., `obj.attr`); it would log dictionary key lookups, not attribute accesses.

143
MCQeasy

A developer uses the .index() method on a string to find the position of a substring. If the substring is not found, what exception is raised?

A.KeyError
B.IndexError
C.ValueError
D.TypeError
AnswerC

str.index() raises ValueError exactly when the substring argument cannot be found in the string. This is the documented, intentional behavior: ValueError signals that a value (the substring) is not present in the sequence. It matches the convention used by list.index() and tuple.index(), which also raise ValueError when the searched element is missing. Unlike .find(), which returns -1 as a sentinel, .index() chooses to raise so you must either catch the exception or check with 'in' first.

Why this answer

The `.index()` method on a string raises a `ValueError` when the specified substring is not found. This is because `ValueError` is the standard Python exception for cases where a function receives an argument with the correct type but an inappropriate value, such as a substring that does not exist in the target string.

Exam trap

The PCAP exam often tests the distinction between `.index()` (raises `ValueError`) and `.find()` (returns `-1`), trapping candidates who confuse the exception type with `IndexError` due to the word 'index' in the method name.

How to eliminate wrong answers

Option A is wrong because `KeyError` is raised when a dictionary key is not found, not when a substring is missing from a string. Option B is wrong because `IndexError` is raised when a sequence index is out of range (e.g., accessing a list element with an invalid index), not when a substring search fails. Option D is wrong because `TypeError` is raised when an operation is applied to an object of inappropriate type (e.g., passing an integer to `.index()` instead of a string), not when the substring is simply absent.

144
MCQmedium

A developer needs to combine a list of 10,000 strings into a single string. Which approach is most efficient in terms of memory and performance?

A.Use ''.join(string_list)
B.Use a loop with str += to concatenate each string
C.Use str.replace() to merge the strings
D.Use str.format() to build the string step by step
AnswerA

The str.join() method is optimized for this exact use case. It first iterates over string_list to calculate the total length, allocates a single backing buffer of exactly that size, and then copies each string into place without creating any intermediate objects. This results in O(n) time and minimal memory overhead, so it is the canonical and most efficient way to concatenate many strings.

Why this answer

The `''.join(string_list)` method is the most efficient because it pre-allocates memory for the final string by first calculating the total length of all strings in the list, then building the result in a single pass. This avoids the quadratic time complexity and repeated memory reallocations caused by string immutability in Python when using `+=` in a loop.

Exam trap

Python Institute often tests the misconception that `+=` is efficient for string concatenation because it works in other languages, but in Python, string immutability makes it a performance disaster for large lists.

How to eliminate wrong answers

Option B is wrong because using `str +=` in a loop creates a new string object for each concatenation, leading to O(n²) time complexity and excessive memory allocation due to Python's immutable strings. Option C is wrong because `str.replace()` is designed for substring replacement, not concatenation, and would require an initial string to operate on, making it unsuitable and inefficient for merging a list of strings. Option D is wrong because `str.format()` is intended for formatting placeholders, not for concatenating an arbitrary list of strings, and using it iteratively would still involve repeated string creation and poor performance.

145
MCQeasy

A developer wants to check if a string contains only alphabetic characters. Which string method should be used?

A.isalnum()
B.isspace()
C.isalpha()
D.isdigit()
AnswerC

isalpha() is correct because it returns True only when the string is non-empty and every character is classified as an alphabetic Unicode character, covering letters like 'A', 'é', and 'α' while rejecting digits, punctuation, and whitespace. This precisely matches the developer's requirement to check if a string contains only alphabetic characters. Note that it is not limited to ASCII, which is useful for internationalized input.

Why this answer

The `isalpha()` method returns `True` if all characters in the string are alphabetic (letters) and the string is non-empty. This directly matches the developer's requirement to check for only alphabetic characters.

Exam trap

The PCAP exam often tests the distinction between `isalpha()` and `isalnum()`, where candidates mistakenly choose `isalnum()` thinking it checks for letters only, but it actually includes digits as well.

How to eliminate wrong answers

Option A is wrong because `isalnum()` returns `True` if all characters are alphanumeric (letters or digits), so it would incorrectly accept strings containing digits. Option B is wrong because `isspace()` returns `True` only if all characters are whitespace, which is irrelevant for checking alphabetic content. Option D is wrong because `isdigit()` returns `True` only if all characters are digits, which is the opposite of the alphabetic check needed.

146
MCQhard

You are working on a legacy system that processes financial transactions. The system uses a class hierarchy: Transaction (base), Deposit, Withdrawal, Transfer. Each subclass overrides a method 'process()' to handle its specific logic. The code often runs in a multi-threaded environment and you notice intermittent errors where a transaction is processed twice. The logging shows that the same transaction object is being passed to the process method multiple times. The transaction objects are created from a factory function that caches recently used transactions. The errors seem to occur when two threads call the factory at the same time with the same parameters. After investigating, you find that the factory uses a class-level dictionary to cache objects. Which of the following is the most appropriate solution to prevent double processing?

A.Add a lock around the cache lookup and creation in the factory function
B.Add a flag to each transaction object to indicate if it has been processed, and check it at the start of process()
C.Remove the caching mechanism from the factory function to ensure new objects are always created
D.Make the process() method idempotent by checking if the transaction has already been applied to the account (e.g., check balance changes)
AnswerD

Idempotency is the correct design because it makes each transaction carry a natural guard: before applying changes, process() can verify whether the transaction's effects are already reflected in the account (e.g., comparing a journal entry, version number, or resulting balance). In a multi-threaded environment, this check must be atomic with the application step—such as using a database transaction with a unique constraint on the transaction ID—so repeated calls from any thread, queue replay, or retry produce only one net effect. This approach is thread-safe, recoverable after crashes, and eliminates the need to force uniqueness at the object or call-site level.

Why this answer

The core issue is that the same transaction object can be processed multiple times in a multi-threaded environment, even if the factory is fixed. Making process() idempotent by checking whether the transaction has already been applied (e.g., verifying account balance changes) ensures that repeated calls with the same object do not cause duplicate financial effects, directly addressing the symptom of double processing regardless of how the object is cached or retrieved.

Exam trap

Python Institute often tests the misconception that preventing object reuse or adding locks in the factory is sufficient to fix double processing, when the real requirement is to make the operation itself idempotent to handle any scenario where the same object is processed more than once.

How to eliminate wrong answers

Option A is wrong because adding a lock around the cache lookup and creation only prevents race conditions in the factory, but does not prevent the same transaction object from being passed to process() multiple times after it has been created; the double processing can still occur if the object is reused or if the calling code erroneously invokes process() again. Option B is wrong because adding a processed flag to the transaction object is not thread-safe without additional synchronization; two threads could both check the flag before either sets it, leading to a race condition where both proceed to process the transaction, and it also violates the principle of keeping processing logic separate from state management. Option C is wrong because removing the caching mechanism eliminates the performance benefit of reusing objects but does not solve the fundamental problem: the same transaction object could still be passed to process() multiple times from other parts of the code, and without idempotency, double processing would still occur.

147
Multi-Selectmedium

Which TWO of the following methods return a boolean value?

Select 2 answers
A.str.upper()
B.str.isspace()
C.str.split()
D.str.join()
E.str.isalpha()
AnswersB, E

str.isspace() is a predicate method that returns True when the string is non-empty and every character in it is a whitespace character (space, tab, newline, carriage return, form feed, and similar Unicode spaces). If the string is empty or contains even one non-whitespace character, it returns False, making it useful for input validation and parsing. Thus, it directly answers the question's requirement of returning a boolean value.

Why this answer

The methods `str.isspace()` and `str.isalpha()` are both string methods that return a boolean value (`True` or `False`) based on whether the string meets specific character classification criteria. `isspace()` returns `True` if all characters in the string are whitespace, while `isalpha()` returns `True` if all characters are alphabetic.

Exam trap

Python Institute often tests the distinction between methods that return a new string or list versus those that return a boolean, leading candidates to mistakenly select methods like `str.upper()` or `str.split()` because they appear to perform a 'check' but actually return a transformed object.

148
Multi-Selecteasy

Which TWO of the following statements about class attributes in Python are true?

Select 2 answers
A.Class attributes are always immutable.
B.Class attributes are shared by all instances.
C.Class attributes are defined inside methods.
D.Modifying a class attribute via an instance modifies it for all instances.
E.Class attributes can be accessed via the class name.
AnswersB, E

Because class attributes live in the class's own namespace, the same object is visible to every instance of that class. When you access `inst.x`, Python first checks the instance's `__dict__`, then walks the class MRO, so if no instance-level attribute shadows it, all instances resolve to the identical class attribute. This shared visibility is the defining trait that distinguishes class attributes from instance attributes, which are stored per-object in each instance's `__dict__`.

Why this answer

Class attributes are defined directly in the class body and are shared across all instances of that class. When you access a class attribute via any instance, Python looks up the attribute in the class's __dict__ if it is not shadowed by an instance attribute, ensuring all instances see the same value unless explicitly overridden.

Exam trap

The PCAP exam often tests the subtle distinction between mutating a mutable class attribute (which affects all instances) and reassigning it via an instance (which creates a shadowing instance attribute), leading candidates to incorrectly think that any modification via an instance changes the class attribute for all instances.

149
MCQmedium

A company is developing a scientific simulation framework where many different solvers must be interchangeable. The framework should enforce that each solver implements methods 'initialize' and 'step'. Developers want to use abstract base classes. Which approach should the team take to ensure that any subclass of 'Solver' cannot be instantiated unless it defines both methods?

A.Define an interface in a separate module and check using isinstance
B.Use @abstractmethod without inheriting from ABC
C.Inherit from ABC and decorate both methods with @abstractmethod
D.Define Solver with methods that raise NotImplementedError
AnswerC

Inheriting from abc.ABC gives the class ABCMeta as its metaclass, which tracks methods marked with @abstractmethod. Any attempt to instantiate Solver itself, or a subclass that does not override both solve() and validate(), raises TypeError: Can't instantiate abstract class with abstract methods. This moves the enforcement to the construction boundary, so the failure is immediate and explicit, and subclass authors are forced to provide concrete implementations before any object can exist.

Why this answer

Inheriting from `ABC` (from the `abc` module) and decorating both `initialize` and `step` with `@abstractmethod` enforces that any concrete subclass must override these methods. Attempting to instantiate a subclass that does not implement all abstract methods raises a `TypeError`, ensuring compile-time-like safety at runtime.

Exam trap

Python Institute often tests the misconception that `@abstractmethod` alone (without inheriting from `ABC`) is sufficient to prevent instantiation, or that raising `NotImplementedError` is equivalent to abstract base class enforcement.

How to eliminate wrong answers

Option A is wrong because using `isinstance` checks against an interface in a separate module does not enforce method implementation at instantiation time; it only checks type membership, and the developer would have to manually verify methods. Option B is wrong because `@abstractmethod` without inheriting from `ABC` has no effect — Python's abstract mechanism only works when the class's metaclass is `ABCMeta` (provided by inheriting from `ABC`). Option D is wrong because defining methods that raise `NotImplementedError` only catches missing implementations at runtime when the method is called, not at instantiation time, and does not prevent instantiation of the class itself.

150
MCQhard

A development team is building a real-time chat application using Python. The application uses a class 'ChatRoom' that maintains a list of 'User' objects as active participants. Each User object holds a reference back to its ChatRoom to send messages. Over time, the application runs out of memory. Profiling reveals that User objects are not being garbage collected even after users disconnect. The team suspects circular references. Which solution would effectively resolve the memory leak without breaking the functionality?

A.Use weakref.WeakSet for the participants list in ChatRoom, so that when a User is no longer referenced elsewhere, it is automatically removed
B.Increase the Python heap size using PYTHON_MALLOC_DEBUG to avoid memory issues
C.Store the ChatRoom reference in User using a weakref.ref, so that the cycle is broken
D.Manually call gc.collect() every time a user disconnects
AnswerA

A WeakSet in ChatRoom holds only weak references to User objects, meaning the set does not participate in reference counting. When the last external strong reference to a User is deleted (e.g., the user logs out and the client connection closes), the object's refcount drops to zero and it is deallocated immediately, automatically removing it from the participants list without any manual cleanup. This breaks the reference cycle between ChatRoom and its participants because the weak reference never increments the reference count, so garbage collection is not needed for removal, and the cycle is resolved as soon as the external strong references vanish.

Why this answer

Using a `weakref.WeakSet` for the participants list in `ChatRoom` means the `ChatRoom` holds only weak references to `User` objects. When a user disconnects and all external references to that `User` are removed, the `User` object becomes unreachable and can be garbage collected, even though the `User` still holds a strong reference back to the `ChatRoom`. This breaks the circular reference without requiring manual intervention or altering the `User`-to-`ChatRoom` relationship.

Exam trap

The key trap here is that weakening the User's reference back to ChatRoom (Option C) does break the circular reference (since one link becomes weak), but it does NOT allow the User to be garbage collected because ChatRoom still holds a strong reference to User. The User remains strongly reachable through ChatRoom, so it stays alive. The correct solution must remove the strong reference from ChatRoom to User, which Option A accomplishes by using a WeakSet for the participants list.

Candidates often mistakenly believe that breaking the cycle from either side is equally effective, overlooking that the strong reference from ChatRoom to User is the one that must be weakened for User objects to be collected.

How to eliminate wrong answers

Option B is wrong because increasing the Python heap size does not resolve the underlying issue of circular references preventing garbage collection; it only delays the inevitable memory exhaustion. Option C is wrong because storing the `ChatRoom` reference in `User` using `weakref.ref` would break the cycle from the `User` side, but the `ChatRoom` still holds strong references to `User` objects in its participants list, so `User` objects would never become unreachable and would still leak. Option D is wrong because manually calling `gc.collect()` does not fix the root cause; the garbage collector can already collect cycles (by default), but if the `User` objects are still strongly referenced from the `ChatRoom` list, they are not garbage, and `gc.collect()` will not remove them.

Page 1

Page 2 of 6

Page 3

All pages