Courseiva

CCNA Object-Oriented Programming Questions

75 of 109 questions · Page 1/2 · Object-Oriented Programming · Answers revealed

1
MCQmedium

Given the following code: class Parent: def show(self): print('Parent') class Child(Parent): def show(self): print('Child') c = Child(); c.show(); super(Child, c).show(). What is the output?

A.Parent Child
B.Child Parent
C.Child Child
D.Parent Parent
AnswerB

This is the correct output. The Child class overrides Parent.show(), so invoking show() on a Child instance dispatches to Child.show() first, printing "Child"; the very next statement in Child.show() is super().show(), which resolves through the method resolution order to Parent.show() and prints "Parent". The override explicitly delegates upward, so the two print calls occur in exactly this order.

Why this answer

The code first calls `c.show()`, which invokes the overridden `show()` method in the `Child` class, printing 'Child'. Then `super(Child, c).show()` calls the `show()` method of the `Parent` class (the superclass of `Child`) on the same instance `c`, printing 'Parent'. Thus the output is 'Child Parent'.

Exam trap

Python Institute often tests the order of execution when `super()` is used with an overridden method, trapping candidates who think `super()` always calls the immediate parent without considering the MRO or who confuse the output order of the two `show()` calls.

How to eliminate wrong answers

Option A is wrong because it reverses the order of the output, mistakenly thinking the superclass method is called first. Option C is wrong because it assumes both calls invoke the `Child` class method, ignoring the effect of `super()` which explicitly calls the parent class method. Option D is wrong because it assumes both calls invoke the `Parent` class method, ignoring that `c.show()` uses the overridden method in `Child`.

2
MCQeasy

A parent class defines a method `move()`. A child class overrides `move()` but also needs to call the parent's version inside it. Which syntax allows that?

A.`self.move()`
B.`super().move()`
C.`Parent.move()`
D.`ParentClass.move(self)`
AnswerB

super().move() constructs a proxy object that starts method resolution in the class hierarchy after the current class, so the call is forwarded to the first matching move defined in that parent chain. Because super() is evaluated with the current class and self injected automatically by the compiler, it requires no explicit self and correctly handles diamond inheritance. It invokes the parent implementation exactly once in the cooperative multiple-inheritance protocol.

Why this answer

`super().move()` is the Python syntax for calling the parent class's `move()` method from within the child class's overridden version. The `super()` function returns a proxy object that delegates method calls to the parent class, following the MRO (Method Resolution Order). This allows the child to extend the parent's behavior without hardcoding the parent class name.

Exam trap

Python Institute often tests the distinction between `super().method()` and calling the parent method by class name, expecting candidates to recognize that `super()` avoids hardcoding and handles multiple inheritance correctly, while `Parent.method(self)` is a valid but less Pythonic alternative that still requires explicit `self`.

How to eliminate wrong answers

Option A is wrong because `self.move()` would call the child's own overridden `move()` method, leading to infinite recursion (a `RecursionError`) since it calls itself again. Option C is wrong because `Parent.move()` is a valid call but requires passing `self` explicitly as an argument; without it, the call will raise a `TypeError` for missing the required positional argument. Option D is wrong because `ParentClass.move(self)` is syntactically correct but uses a hardcoded parent class name, which violates the principle of polymorphism and makes the code less maintainable if the parent class name changes; `super()` is the preferred, dynamic approach.

3
MCQeasy

A junior developer writes a class 'Logger' that should only ever have one instance (singleton). They attempt to implement it by overriding __new__ to always return the same instance. However, when multiple threads attempt to create a Logger, they sometimes get different instances. Which modification will make the singleton thread-safe?

A.Use a lock (threading.Lock) in __new__ to serialize access
B.Use a class method get_instance() that checks a class variable and creates the instance if needed, and call that from __init__
C.Use a metaclass that overrides __call__ to return the singleton
D.Override __init__ to check if the instance was already initialized and if so, skip initialization
AnswerA

Acquiring a threading.Lock inside __new__ before checking and assigning the class-level singleton reference serializes the critical section, so concurrent threads cannot both observe a None value and proceed to construct separate instances. Without this lock, even with CPython's GIL, a thread can be suspended between the check and the assignment, allowing another thread to create a second instance. This approach directly addresses the race condition at the point where the object is actually allocated and published.

Why this answer

The race condition occurs when multiple threads simultaneously check `cls._instance` and find it `None`, then both proceed to create a new instance. Wrapping the creation logic inside a `threading.Lock` in `__new__` ensures that only one thread can execute the critical section at a time, guaranteeing that only one instance is ever created.

Exam trap

Python Institute often tests the misconception that simply overriding `__new__` or using a class method is sufficient for thread safety, when in fact the race condition in the check-then-create pattern requires explicit synchronization like a lock.

How to eliminate wrong answers

Option B is wrong because calling a class method from `__init__` does not prevent the race condition; `__init__` is still called on every instantiation attempt, and the check-then-create pattern in the class method is itself not thread-safe without a lock. Option C is wrong because a metaclass overriding `__call__` can implement a singleton, but it does not inherently provide thread safety unless the metaclass itself uses a lock or other synchronization mechanism. Option D is wrong because overriding `__init__` to skip initialization does not prevent multiple instances from being created; `__new__` still returns a new object each time, and the singleton pattern requires controlling instance creation, not just initialization.

4
MCQhard

Refer to the exhibit. What is the output? (Note: actual MRO may vary; choose the one that matches Python 3 C3 linearization.)

A.(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>)
B.(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
C.(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
D.(<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)
AnswerB

This is the exact tuple produced by C3 linearization for class D(B, C), where both B and C inherit from A. The merge step selects B first because it is the declared first base of D and is not a tail of any other candidate list; it then selects C, followed by A, and finally object. This order respects both the local precedence D(B, C) and the monotonicity rule that the MROs of B and C remain prefixes of D's MRO.

Why this answer

Python 3 uses C3 linearization to compute the Method Resolution Order (MRO). For class D inheriting from B and C, which both inherit from A, the MRO is D, B, C, A, object. This satisfies the monotonicity and local precedence order: B comes before C (as per D's bases), and A is last among the user-defined classes, with object always appended.

Exam trap

Python Institute often tests whether candidates remember that `object` is always the last class in the MRO for new-style classes in Python 3, and that the local precedence order of base classes (left-to-right in the class definition) must be strictly followed in the linearization.

How to eliminate wrong answers

Option A is wrong because it omits the <class 'object'> at the end; in Python 3, every class implicitly inherits from object, so the MRO always includes object as the final entry. Option C is wrong because it places C before B, violating the local precedence order of D's bases (B, C) — C3 linearization respects the order in which base classes are listed. Option D is wrong because it places A before B and C, which violates the rule that a parent class must appear after all its subclasses in the MRO; since B and C both inherit from A, A must come after both.

5
Drag & Dropmedium

Drag and drop the steps to define and call a function with default arguments 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

To define a function with default arguments in Python, first use the def keyword, then provide the function name and parentheses. Inside the parentheses, specify any parameters, optionally assigning default values using the assignment operator. Follow with a colon and indent the function body.

Finally, call the function by its name, passing arguments as needed. The correct order is 1,2,3,4,5,6.

Exam trap

A common mistake is to place the colon before the parameter list or to forget to indent the body. Ensure the colon is right after the parentheses and the body is indented.

6
MCQeasy

A programmer writes a class with a method that should be called on the class itself, not on instances. Which decorator is appropriate?

A.@property
B.@classmethod
C.@abstractmethod
D.@staticmethod
AnswerB

The `@classmethod` decorator binds the method to the class rather than to an instance, automatically receiving the class (`cls`) as the first parameter instead of `self`. This satisfies the stem’s constraint that the method “should be called on the class itself, not on instances,” because the decorator ensures the method can be invoked directly via `ClassName.method()` without requiring an object.

Why this answer

The @classmethod decorator transforms a method so that it receives the class itself as the first implicit argument (cls), rather than an instance (self). This allows the method to be called on the class directly, e.g., MyClass.my_method(), and is the correct choice when a method should operate on the class level, not on instances.

Exam trap

Python Institute often tests the distinction between @classmethod and @staticmethod, trapping candidates who think both are interchangeable for class-level calls, but @staticmethod does not receive the class argument and cannot modify class state.

How to eliminate wrong answers

Option A is wrong because @property is used to define a method that can be accessed like an attribute, typically on an instance, and does not allow calling on the class itself. Option C is wrong because @abstractmethod is used to declare a method as abstract in an abstract base class, requiring subclasses to implement it; it does not control whether the method is called on the class or instance. Option D is wrong because @staticmethod defines a method that does not receive any implicit first argument (neither self nor cls), so it can be called on both instances and the class, but it does not receive the class as an argument, making it unsuitable when the method needs to access or modify class-level state.

7
MCQhard

A class 'MyClass' has a method 'do_something' that uses 'self.__private'. A subclass 'MySubClass' tries to access 'self.__private' and gets an AttributeError. Why?

A.Because the attribute is defined as a class attribute, not instance attribute
B.Because the subclass overrides the method that uses the attribute
C.Because name mangling renames the attribute to _MyClass__private, and the subclass implicitly accesses _MySubClass__private
D.Because the attribute is private and not inherited
AnswerC

In Python, any attribute name written with two leading underscores inside class MyClass is transformed at compile time to _MyClass__private, a process called name mangling. When the same spelling __private appears in the body of a subclass, the compiler uses the subclass's name, producing _MySubClass__private; since that attribute was never assigned, self.__private raises AttributeError. The base class value still exists, but only under the base-mangled key _MyClass__private.

Why this answer

Python's name mangling mechanism renames any attribute prefixed with double underscores (like `__private`) in a class definition to `_ClassName__private`. When `MySubClass` tries to access `self.__private`, Python looks for `_MySubClass__private`, which does not exist, causing an AttributeError. The attribute `_MyClass__private` is still accessible from the subclass, but only via its mangled name.

Exam trap

Python Institute often tests the misconception that double underscore attributes are truly private and not inherited, when in fact they are inherited under a mangled name, and the error arises from the subclass attempting to access the unmangled name.

How to eliminate wrong answers

Option A is wrong because the error occurs regardless of whether `__private` is a class or instance attribute; name mangling applies to both. Option B is wrong because the error is not due to method overriding; the subclass does not need to override any method to trigger the AttributeError—it simply tries to access the mangled attribute directly. Option D is wrong because Python does not enforce true private attributes; name mangling provides name obfuscation, not access control, and the attribute is inherited (under its mangled name), so the statement that it is 'not inherited' is incorrect.

8
MCQeasy

A developer wants to ensure that an attribute 'balance' of a BankAccount class cannot be accessed directly from outside the class but can be accessed through a method. Which approach should be used?

A.Declare 'self.balance' as a class variable
B.Declare 'self.balance' and provide a getter method
C.Declare 'self._balance' and provide a getter method
D.Declare 'self.__balance' and provide a getter method
AnswerD

Assigning self.__balance inside the class triggers Python's name mangling: the attribute is stored as _ClassName__balance, making it impossible (or at least very awkward) for external code to access it by the original name. The getter then provides the only controlled public way to read its value, and a matching setter or property can validate changes before writing. This combination actually ensures encapsulation, which is why it meets the developer's requirement.

Why this answer

Prefixing the attribute with double underscores (`__balance`) triggers Python's name mangling, which renames the attribute to `_BankAccount__balance` at runtime. This prevents direct access from outside the class (e.g., `obj.balance` raises an `AttributeError`), while a getter method (e.g., `get_balance()`) can still retrieve the value. This is the standard Python idiom for achieving 'weak' private encapsulation in OOP.

Exam trap

Python Institute often tests the distinction between single underscore (`_`) as a convention versus double underscore (`__`) as name mangling, and candidates mistakenly believe that a single underscore provides actual access restriction.

How to eliminate wrong answers

Option A is wrong because declaring `self.balance` as a class variable (e.g., `balance = 0` at class level) does not prevent direct access; it is still accessible via `obj.balance` and can be modified externally. Option B is wrong because `self.balance` (without underscore) is a public attribute; even with a getter method, the attribute remains directly accessible and modifiable from outside the class, defeating encapsulation. Option C is wrong because `self._balance` (single underscore) is a naming convention for 'protected' attributes, but it does not enforce any access restriction—Python still allows direct access (e.g., `obj._balance`), and the getter method does not prevent that.

9
MCQmedium

A developer designs a plugin system where each plugin must implement a method 'execute'. Which code snippet correctly enforces that subclasses provide an implementation using the 'abc' module?

A.from abc import abstractmethod class Plugin: @abstractmethod def execute(self): pass
B.from abc import ABC class Plugin(ABC): def execute(self): pass
C.from abc import ABC, abstractmethod class Plugin(ABC): @abstractmethod def execute(self): pass
D.class Plugin: def execute(self): raise NotImplementedError
AnswerC

Subclassing ABC and decorating execute with @abstractmethod tells ABCMeta to record execute in Plugin.__abstractmethods__, making Plugin itself an abstract class that cannot be instantiated. Any concrete subclass must provide a concrete override of execute before Python permits instantiation; otherwise, it receives a TypeError with a message about abstract methods. This enforces the plugin contract early, at object construction time, guaranteeing that every plugin instance has a working execute method.

Why this answer

It uses both `ABC` as the metaclass and `@abstractmethod` decorator to enforce that subclasses must override the `execute` method. Without `ABC`, the `@abstractmethod` decorator has no effect; without `@abstractmethod`, the method is just a regular method that can be inherited without being overridden. This combination ensures that any concrete subclass that does not implement `execute` will raise a `TypeError` at instantiation time.

Exam trap

Python Institute often tests the misconception that importing `abstractmethod` alone is sufficient to enforce abstraction, or that raising `NotImplementedError` in a base method is equivalent to using the `abc` module, when in fact only the combination of `ABC` and `@abstractmethod` provides compile-time-like enforcement at instantiation.

How to eliminate wrong answers

Option A is wrong because it imports only `abstractmethod` but does not make `Plugin` inherit from `ABC`, so the `@abstractmethod` decorator is ignored and subclasses are not forced to implement `execute`. Option B is wrong because it inherits from `ABC` but does not decorate `execute` with `@abstractmethod`, making it a regular method that subclasses can optionally override — no enforcement occurs. Option D is wrong because it uses a runtime `NotImplementedError` which only raises an error if the method is actually called, not at instantiation time, and it does not use the `abc` module at all, so the question's requirement to use the `abc` module is not met.

10
MCQhard

Refer to the exhibit. What is printed?

A.3\n6
B.3\nError
C.Error\n3
D.3\n3
AnswerD

On the first invocation, the function computes and returns 3, and the cache stores that result keyed by the argument. The second invocation sees the argument already in the cache and immediately returns the stored value 3 without re-entering the function. This behavior is exactly what caching decorators like `functools.lru_cache` provide, making the output consistent.

Why this answer

The code defines a class `A` with a class attribute `x = 3`. Inside `__init__`, the first `print(self.x)` accesses the class attribute (since no instance attribute exists yet), printing `3`. The statement `self.x += 1` is equivalent to `self.x = self.x + 1`; it reads the class attribute for the right-hand side, evaluates to `4`, and then creates a new instance attribute `x` with value `4`, shadowing the class attribute.

The second `print(A.x)` explicitly accesses the class attribute, which remains `3`. Hence the output is `3` and `3` on separate lines.

Exam trap

Python Institute often tests the subtle difference between class attributes and instance attributes, specifically that `self.x += 1` creates a new instance attribute rather than modifying the class attribute, leading candidates to mistakenly think the class attribute itself is incremented.

How to eliminate wrong answers

Option A is wrong because it suggests the second value is 6, which would require the increment to be applied twice or a different operation. Option B is wrong because it suggests an error occurs after printing 3, but no error occurs; the code runs successfully. Option C is wrong because it suggests an error is printed first, but the first print statement executes without error, printing 3.

11
MCQmedium

Given that MyClass defines __private_attr in __init__, why does this error occur?

A.The attribute name is mangled to _MyClass__private_attr.
B.The attribute was not defined in __init__.
C.Private attributes cannot be accessed outside the class.
D.The attribute is a class attribute not an instance attribute.
AnswerA

Python applies name mangling to any identifier of the form __spam (with at most one trailing underscore) that occurs inside a class definition, rewriting it to _ClassName__spam. So self.__private_attr declared in __init__ is stored as self._MyClass__private_attr by the compiler. From outside the class, you must use that mangled name; accessing __private_attr directly will fail because no such attribute exists under that literal name.

Why this answer

Python uses name mangling for attributes with double underscores (__) to avoid accidental overriding in subclasses. When you define __private_attr inside __init__, Python internally renames it to _MyClass__private_attr. Attempting to access obj.__private_attr from outside the class fails because that mangled name is not recognized, leading to an AttributeError.

Exam trap

The Python Institute often tests the misconception that double underscores create truly private attributes, leading candidates to choose 'Private attributes cannot be accessed outside the class' when the real issue is name mangling and the attribute still being accessible via the mangled name.

How to eliminate wrong answers

Option B is wrong because the attribute __private_attr is indeed defined in __init__; the error is not due to missing definition but due to name mangling. Option C is wrong because Python does not enforce true private access; the attribute can still be accessed using the mangled name _MyClass__private_attr, so the statement 'cannot be accessed outside the class' is technically false. Option D is wrong because __private_attr is assigned to self inside __init__, making it an instance attribute, not a class attribute.

12
MCQmedium

Refer to the exhibit. What will be the output when the code is executed?

A.AttributeError
B.None
C.1
D.2
AnswerD

The instance attribute x is assigned 2 in B.__init__ immediately after super().__init__() returns. Because Python resolves instance attributes dynamically at access time, print(b.x) sees the final binding of x on that specific instance, which is 2, not the intermediate value 1 set by the parent class.

Why this answer

The code defines a class `MyClass` with a class attribute `x = 1`. The `__init__` method sets an instance attribute `self.x = 2`. When `obj.x` is accessed, Python first looks for an instance attribute, finding `self.x = 2`, so it prints `2`.

Option D is correct because instance attributes shadow class attributes.

Exam trap

The trap here is that candidates often confuse class attributes with instance attributes, assuming `x = 1` is always returned, but Python's attribute lookup prioritizes instance attributes over class attributes when both exist.

How to eliminate wrong answers

Option A is wrong because there is no AttributeError; the attribute `x` exists both as a class attribute and an instance attribute, so access succeeds. Option B is wrong because `print(obj.x)` does not return `None`; it prints the integer `2`. Option C is wrong because `1` is the class attribute value, but the instance attribute `self.x = 2` takes precedence during attribute lookup.

13
MCQhard

A programmer wants to restrict a class to only allow specific attribute names and reduce memory usage. Which feature should they use?

A.Define `__slots__` as a tuple of allowed attribute names.
B.Override `__init_subclass__` to enforce restrictions.
C.Use @property for every attribute.
D.Define `__dict__` as a class variable.
AnswerA

Defining `__slots__` as a tuple of allowed attribute names is the correct approach because it creates internal descriptors for only those names and suppresses the automatic per-instance `__dict__`. As a result, assigning any attribute not listed in the tuple raises `AttributeError`, so the class is strictly limited to the declared fields. This also reduces memory overhead, which is a useful side effect, but the restriction is enforced at the language level.

Why this answer

Defining `__slots__` as a tuple of allowed attribute names restricts the class to only those attributes, preventing the creation of a per-instance `__dict__` and thereby reducing memory usage. This is a built-in Python mechanism that overrides the default dynamic attribute storage, making it ideal for memory-constrained applications.

Exam trap

Python Institute often tests the misconception that `__slots__` is only about restricting attribute names, but the trap here is that candidates may overlook its primary purpose of memory optimization, leading them to choose options like `@property` or `__init_subclass__` that address access control but not memory reduction.

How to eliminate wrong answers

Option B is wrong because `__init_subclass__` is a hook for customizing subclass creation, not for restricting attribute names on instances of the class itself. Option C is wrong because using `@property` for every attribute does not prevent the creation of arbitrary instance attributes; it only controls access to specific ones, and it does not reduce memory usage (each property still relies on the instance `__dict__` or slots). Option D is wrong because defining `__dict__` as a class variable does not restrict attribute names; it actually encourages dynamic attribute storage and increases memory overhead, as each instance would still have its own `__dict__`.

14
MCQmedium

A developer creates classes `A`, `B(A)`, `C(A)`, and `D(B, C)`. When calling a method from `D` that is defined in `A`, which class's version is used according to Python's MRO?

A.The method from B, because it is the first parent.
B.The method from C, because it appears after B.
C.The method from A, but only if B and C do not override it.
D.The method from A, found through B then C.
AnswerD

The C3 linearization algorithm for class D(B, C), where B and C both inherit from A, produces the MRO D -> B -> C -> A. The attribute lookup follows this exact sequence: it checks D, then B, then C, and finally A; since neither B nor C defines the method, the first defining class encountered is A. Thus the method is found on A after the traversal through B and C.

Why this answer

Python's Method Resolution Order (MRO) for class `D(B, C)` follows the C3 linearization algorithm, which ensures a depth-first left-to-right search while preserving monotonicity. For `D(B, C)`, the MRO is `D -> B -> C -> A`, so a method defined in `A` that is not overridden in `B` or `C` will be found via `B` first, then `C`, and finally `A`. Option D correctly states that the method from `A` is used, found through `B` then `C`, which matches the actual resolution path.

Exam trap

Python Institute often tests the misconception that Python's MRO simply searches the first parent and its ancestors before moving to the next parent (depth-first left-to-right), but the actual C3 linearization can produce a different order, especially in diamond inheritance, and candidates may incorrectly assume the method from `A` is found directly without considering the intermediate classes in the MRO.

How to eliminate wrong answers

Option A is wrong because it assumes the first parent's version is always used, but Python's MRO does not simply stop at the first parent; it uses C3 linearization to consider all ancestors in a specific order, and if `B` does not override the method, the search continues to `C` and then `A`. Option B is wrong because it incorrectly suggests that `C`'s version is used because it appears after `B` in the class definition, but the MRO for `D(B, C)` is `D -> B -> C -> A`, so `B` is checked before `C`, and the method from `A` is only reached if neither `B` nor `C` overrides it. Option C is wrong because it implies the method from `A` is used only if `B` and `C` do not override it, which is true, but it omits the critical detail that the resolution path goes through `B` then `C` before reaching `A`, and the statement 'found through B then C' is essential to understanding MRO; the option as phrased is incomplete and misleading.

15
MCQhard

Refer to the exhibit. What happens when the last command is executed?

A.It creates a Circle object successfully
B.It prints nothing and exits with code 0
C.It raises TypeError: Can't instantiate abstract class Circle with abstract methods area
D.It raises AttributeError: 'Circle' object has no attribute 'area'
AnswerC

When a class inherits from ABC and contains an @abstractmethod, Python's ABCMeta checks at instantiation time whether any abstract methods remain unimplemented. Because Circle defines area as abstract and the exhibit does not override it, calling Circle() raises TypeError: Can't instantiate abstract class Circle with abstract methods area. This exactly reflects the intended behavior of the abc module, making this option correct.

Why this answer

The last command attempts to instantiate an abstract class `Circle` that has an abstract method `area` which has not been implemented. In Python, abstract classes defined with `ABC` and decorated with `@abstractmethod` cannot be instantiated directly; attempting to do so raises `TypeError` with the message 'Can't instantiate abstract class Circle with abstract methods area'.

Exam trap

The Python Institute's PCAP exam often tests the distinction between `TypeError` for abstract class instantiation and `AttributeError` for missing attributes on an existing object, trapping candidates who confuse the timing of the error (instantiation vs. method call).

How to eliminate wrong answers

Option A is wrong because the `Circle` class is abstract (inherits from `ABC` and has an `@abstractmethod` decorator on `area`), so it cannot be instantiated; a `TypeError` is raised instead of creating an object. Option B is wrong because the command does not execute successfully; it raises an exception, so the program does not print nothing and exit with code 0. Option D is wrong because the error is not an `AttributeError` about a missing attribute on an instance; the error occurs at instantiation time due to the abstract method, not after the object is created.

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

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

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

19
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).

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

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

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

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

24
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`.

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

26
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).

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

28
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`.

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

30
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).

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

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

33
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`.

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

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

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

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

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

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

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

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

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

43
MCQeasy

A class `Point` has an `__init__` that sets `self.x` and `self.y`. They want to compare points by equality (i.e., `p1 == p2` should work correctly). Which method should they implement?

A.`__same__`
B.`__eq__`
C.`__compare__`
D.`__cmp__`
AnswerB

`__eq__` is the correct special method to override for the equality operator (`==`) in Python. By implementing `__eq__` in the `Point` class, you can compare two point instances based on their coordinate attributes (e.g., `self.x == other.x and self.y == other.y`) rather than by their identity. This is the standard, documented approach for value equality in Python's object model.

Why this answer

In Python, the `__eq__` method is the correct way to define equality comparison for objects. When you implement `__eq__(self, other)`, it is automatically called by the `==` operator, allowing `p1 == p2` to return a Boolean based on custom logic (e.g., comparing `self.x` and `self.y`). This is the standard Python protocol for equality testing.

Exam trap

Python Institute often tests the distinction between Python 2's `__cmp__` and Python 3's `__eq__`; the trap here is that candidates familiar with older Python may incorrectly choose `__cmp__`, not realizing it is obsolete in Python 3.

How to eliminate wrong answers

Option A is wrong because `__same__` is not a Python special method; Python uses `__eq__` for equality, not `__same__`. Option C is wrong because `__compare__` is not a Python dunder method; Python uses `__eq__` for equality and `__lt__`, `__gt__`, etc. for ordering. Option D is wrong because `__cmp__` was used in Python 2 for comparison (returning -1, 0, 1) but was removed in Python 3; the PCAP exam focuses on Python 3, where `__eq__` is the correct method for equality.

44
MCQhard

Which of the following correctly uses an abstract base class to enforce that all subclasses implement a 'make_sound' method? (Assume ABC imported)

A.from abc import ABC, abstractmethod\nclass Animal(ABC):\n @abstractmethod\n def make_sound(self):\n pass
B.from abc import abstractmethod\nclass Animal:\n @abstractmethod\n def make_sound(self):\n pass
C.class Animal:\n def make_sound(self):\n return None
D.class Animal:\n def make_sound(self):\n raise NotImplementedError
AnswerA

Decorating `make_sound` with `@abstractmethod` inside a class inheriting from `ABC` registers it as abstract, so Python raises `TypeError` when any subclass omits the override or when `Animal` itself is instantiated. This directly enforces the stem's requirement that every subclass implement `make_sound`.

Why this answer

It imports both `ABC` and `abstractmethod` from the `abc` module, defines `Animal` as a subclass of `ABC`, and decorates `make_sound` with `@abstractmethod`. This combination prevents instantiation of `Animal` and forces any concrete subclass to override `make_sound`, or else a `TypeError` is raised at instantiation time.

Exam trap

Python Institute often tests whether candidates know that `@abstractmethod` alone does not make a class abstract — the class must explicitly inherit from `ABC` (or have its metaclass set to `ABCMeta`), otherwise the decorator is ignored and instantiation is allowed.

How to eliminate wrong answers

Option B is wrong because it does not make `Animal` a subclass of `ABC`; without inheriting from `ABC`, the `@abstractmethod` decorator has no effect and the class can be instantiated directly, so no enforcement occurs. Option C is wrong because it defines a concrete method that simply returns `None`; subclasses are free to ignore it, and there is no abstract mechanism to require overriding. Option D is wrong because raising `NotImplementedError` is a runtime convention, not a compile-time or instantiation-time enforcement; a subclass that forgets to override `make_sound` will only fail when the method is called, not when the object is created, and the base class is not abstract.

45
Multi-Selecteasy

Which TWO of the following are special methods in Python?

Select 2 answers
A.`__bar__`
B.`__main__`
C.`__init__`
D.`__str__`
E.`__foo__`
AnswersC, D

`__init__` is a special (dunder) method automatically invoked by Python when an instance is created, initialising the object's attributes. It satisfies the stem's requirement for special methods, which are defined by reserved double-underscore names and called implicitly by the interpreter rather than by explicit invocation.

Why this answer

In Python, special methods (also called dunder or magic methods) are predefined methods with names of the form __name__ that the interpreter invokes implicitly to implement language features. Option C, `__init__`, is the initializer special method that Python calls automatically when an instance is created, e.g., `obj = MyClass()`, to set up the object's initial state. Option D, `__str__`, is the special method that Python calls by default by `str(obj)` and `print(obj)` to produce a human-readable string representation of the object.

By contrast, options A (`__bar__`) and E (`__foo__`) merely follow the double-underscore naming pattern but are not defined by the Python data model as special methods, so they are ordinary attributes. Option B, `__main__`, is not a method at all; it is the name of the top-level execution scope (the value of `__name__` for the main script) and the conventional module name checked with `if __name__ == "__main__":`.

Exam trap

Python Institute often tests the distinction between actual special methods (like `__init__` and `__str__`) and arbitrary dunder-named attributes that are not part of Python's language specification, leading candidates to mistakenly think any name with double underscores is a special method.

46
MCQmedium

Given: class A: def method(self): print('A'); class B(A): def method(self): super().method(); print('B'); class C(A): def method(self): super().method(); print('C'); class D(B, C): pass. What is printed by D().method()?

A.A B C
B.A C B
C.C A B
D.B A C
AnswerB

The MRO for D is D, B, C, A, so `super()` inside B resolves to C, not A. B's `super().method()` therefore invokes C's method, which prints 'C' after its own `super()` call reaches A and prints 'A'. Control then returns to B, printing 'B', yielding A C B.

Why this answer

Python's MRO (Method Resolution Order) for class D, which inherits from B and C (both inheriting from A), follows the C3 linearization algorithm. The MRO for D is D -> B -> C -> A, so calling D().method() triggers B.method(), which calls super().method() (resolving to C.method()), which calls super().method() (resolving to A.method()), printing 'A', then back to C prints 'C', then back to B prints 'B', resulting in 'A C B'.

Exam trap

Python Institute often tests the misconception that super() always calls the immediate parent class (A) in a linear chain, rather than following the full MRO, leading candidates to pick 'A B C' instead of the correct 'A C B'.

How to eliminate wrong answers

Option A is wrong because it assumes a simple left-to-right depth-first order without considering that super() in B resolves to C (the next class in MRO), not directly to A, so the output is not 'A B C'. Option C is wrong because it incorrectly suggests C.method() is called first, but the MRO starts with D, then B, not C. Option D is wrong because it implies B.method() prints 'B' before its super() chain completes, but the actual order is A (from A.method), then C (from C.method), then B (from B.method).

47
Multi-Selecteasy

Which TWO of the following are valid ways to define a property in a Python class? (Select exactly 2.)

Select 2 answers
A.Define a method named `property` inside the class.
B.Override `__getattribute__` and check the attribute name.
C.Override `__getattr__` to simulate a property.
D.Use `property(getter, setter)` as a class variable.
E.Use the @property decorator on a method.
AnswersD, E

Assigning `property(getter, setter)` as a class variable directly creates a data descriptor that manages a specific attribute. Because `property` is a class implementing `__get__` and `__set__`, Python calls the supplied getter and setter functions whenever the attribute is accessed or assigned. This functional form is valid and is equivalent to the decorator form, though less commonly used in modern code.

Why this answer

`property(getter, setter)` is a built-in function that returns a property object, which can be assigned as a class variable to define a managed attribute. Option E is correct because the `@property` decorator is the standard, concise way to define a read-only property by decorating a method that acts as the getter.

Exam trap

Python Institute often tests the distinction between defining a property via the `property()` function or `@property` decorator versus overriding dunder methods like `__getattr__` or `__getattribute__`, which are attribute interception hooks, not property definitions.

48
Multi-Selecthard

Which THREE statements about the Python method resolution order (MRO) are true? (Select exactly 3.)

Select 3 answers
A.The MRO is determined at runtime when a method is called.
B.The MRO of a class can be viewed using the __mro__ attribute.
C.C3 linearization is the algorithm used for MRO in Python 3.
D.Python uses a depth-first left-to-right algorithm for MRO.
E.super() uses the MRO to determine which method to call.
AnswersB, C, E

Every Python class exposes a read-only __mro__ attribute that is a tuple of classes in the exact order Python will search for attributes and methods. The tuple starts with the class itself, then its ancestors in C3-linearized order, and ends with object. This attribute is the canonical way to inspect the method resolution order, for example by printing MyClass.__mro__.

Why this answer

The `__mro__` attribute on a class returns a tuple of classes in the exact order that Python uses to resolve methods and attributes. This attribute is computed at class definition time using the C3 linearization algorithm, and it provides a direct, read-only view of the resolution order for that class.

Exam trap

Python Institute often tests the misconception that MRO is determined dynamically at runtime (option A) or that Python still uses a simple depth-first left-to-right algorithm (option D), when in fact Python 3 exclusively uses the C3 linearization algorithm computed at class definition time.

49
Multi-Selecteasy

Which TWO statements about static methods (@staticmethod) are correct?

Select 2 answers
A.They do not receive an implicit first argument.
B.They can access instance attributes via self.
C.They can access class variables only via cls.
D.They are defined using the @staticmethod decorator.
E.They can only be defined inside a metaclass.
AnswersA, D

Static methods are not bound to either an instance or the class, so Python passes no implicit first argument when they are invoked. Unlike instance methods which receive self or class methods which receive cls, a static method's signature matches exactly what is written in the function definition. This makes them behave like ordinary functions, but they still live in the class's namespace.

Why this answer

Static methods in Python do not receive an implicit first argument like `self` (for instance methods) or `cls` (for class methods). They behave like plain functions but belong to a class's namespace, and are called without any automatic parameter injection.

Exam trap

Python Institute often tests the distinction between `@staticmethod` and `@classmethod`, and the trap here is that candidates confuse static methods with class methods, assuming static methods can access class variables via `cls` or instance attributes via `self`.

50
Multi-Selecthard

Which THREE of the following are true about Python's object-oriented programming features?

Select 3 answers
A.Python supports method overloading based on argument types
B.Python supports multiple inheritance
C.All methods are virtual in the sense that they can be overridden
D.Python enforces access modifiers like private and protected
E.Operator overloading can be implemented by defining special methods like __add__
AnswersB, C, E

Multiple inheritance is a first-class feature in Python: class Derived(Base1, Base2) creates a class that inherits attributes and methods from all listed base classes. To resolve conflicts, Python computes a linearization order (MRO) using the C3 algorithm, which determines the sequence in which base classes are searched for attributes. This MRO also enables cooperative behavior with super(), making mixin classes a common and safe pattern, though diamond hierarchies require deliberate design.

Why this answer

Python's class hierarchy supports multiple inheritance, allowing a class to inherit from more than one parent class. This is a core feature of Python's object-oriented programming model, implemented via the C3 linearization algorithm (Method Resolution Order, or MRO) to resolve method and attribute lookups unambiguously.

Exam trap

Python Institute often tests the misconception that Python supports method overloading like Java or C++, leading candidates to incorrectly select Option A, when in fact Python uses dynamic typing and late binding to handle different argument patterns through default or variable arguments.

51
Multi-Selecteasy

Which of the following statements about class inheritance in Python are true? (Choose two.)

Select 2 answers
A.A class can inherit from only one base class.
B.Abstract base classes can be defined using the ABC module.
C.A child class can override any method from its parent class, but only if the method is declared as virtual.
D.The super() function is used to call a method from a sibling class.
E.Python supports multiple inheritance.
AnswersB, E

The abc module in Python's standard library supplies the ABC metaclass-derived helper class and the abstractmethod decorator for defining abstract base classes. Marking a method with @abstractmethod forces any concrete subclass to implement it before it can be instantiated; attempting to instantiate an unfinished subclass raises TypeError. This gives developers a robust mechanism for enforcing interface contracts in a dynamically typed language.

Why this answer

Python's `abc` module (Abstract Base Classes) allows you to define abstract base classes by inheriting from `ABC` and using the `@abstractmethod` decorator. This enforces that subclasses must implement the abstract methods, providing a formal interface contract.

Exam trap

Python Institute often tests the misconception that `super()` only calls a parent method, but in multiple inheritance it actually calls the next class in the MRO, which could be a sibling or a cousin, not necessarily a direct parent.

52
MCQeasy

A developer is implementing a simple counter class. The class should start at 0 and increment by 1 each time the 'increment' method is called. Which implementation is correct?

A.class Counter:\n def __init__(self):\n self.count = 0\n def increment(self):\n count = self.count + 1
B.class Counter:\n def __init__(self):\n self.count = 0\n def increment(self):\n self.count += 1
C.class Counter:\n def __init__(self):\n count = 0\n def increment(self):\n count += 1
D.class Counter:\n def __init__(self):\n self.count = 0\n def increment():\n self.count += 1
AnswerB

This is the correct implementation because self.count += 1 is syntactic sugar for self.count = self.count + 1, which rebinds the instance attribute to its own current value plus one. The method receives the instance through the mandatory self parameter, so each call persists the updated count on the object. This follows the standard pattern for mutating instance state and produces the expected cumulative behavior across multiple calls.

Why this answer

It properly initializes an instance variable `self.count` to 0 in the `__init__` method and then uses `self.count += 1` in the `increment` method to modify the instance variable in place. The `+=` operator is a shorthand for `self.count = self.count + 1`, which correctly updates the counter each time `increment` is called.

Exam trap

Python Institute often tests the distinction between local variables and instance variables in methods, trapping candidates who forget to prefix `self` when accessing or modifying object attributes.

How to eliminate wrong answers

Option A is wrong because `count = self.count + 1` creates a local variable `count` inside the `increment` method instead of updating the instance variable `self.count`, so the counter never changes. Option C is wrong because `count = 0` in `__init__` creates a local variable instead of an instance variable, and `count += 1` in `increment` tries to modify a local variable that hasn't been initialized in that scope, causing a `NameError`. Option D is wrong because the `increment` method is missing the required `self` parameter, so calling `increment()` will raise a `TypeError` about missing positional arguments.

53
MCQeasy

A programmer wants to ensure that a class attribute is the same for all instances and can be accessed via the class name. Which type of variable should be defined?

A.Global variable
B.Instance variable
C.Local variable inside a method
D.Class variable
AnswerD

A class variable is defined directly in the class body, making it part of the class namespace and shared by all instances of that class. It can be accessed via ClassName.variable or through an instance (unless shadowed by an instance attribute), and changes made through the class are visible to every instance. This matches the requirement for a class attribute that is common to the class rather than per-instance.

Why this answer

A class variable in Python is defined directly within the class body (outside any method) and is shared across all instances. It can be accessed via the class name (e.g., `ClassName.var`) or through any instance, ensuring the same value for all objects.

Exam trap

Python Institute often tests the misconception that a class variable can be safely modified via an instance, but the trap is that doing so creates an instance variable that shadows the class variable, leaving the original class variable unchanged for other instances.

How to eliminate wrong answers

Option A is wrong because a global variable is defined at the module level, not inside a class, and is not inherently tied to the class or its instances; it can be modified from anywhere, breaking encapsulation. Option B is wrong because an instance variable is unique to each object (defined with `self` in `__init__`), so it is not the same for all instances. Option C is wrong because a local variable inside a method exists only within that method's scope and cannot be accessed via the class name or by other methods.

54
MCQhard

A team is developing a banking system in Python. They have a base class Account with attributes account_number and balance, and a method __init__(self, account_number, balance) that initializes these attributes. They then create a subclass SavingsAccount that adds an attribute interest_rate. In SavingsAccount's __init__, they assign self.interest_rate = rate but do not call super().__init__. When they instantiate SavingsAccount('12345', 1000, 0.02) and attempt to print(balance), an AttributeError occurs: 'SavingsAccount' object has no attribute 'balance'. What is the most appropriate fix to ensure that the SavingsAccount includes balance and account_number without code duplication?

A.Remove the __init__ method from SavingsAccount entirely and rely on the default __init__ from Account.
B.Manually assign self.account_number and self.balance in SavingsAccount.__init__ before assigning interest_rate.
C.Call super().__init__(account_number, balance) as the first line in SavingsAccount.__init__, then assign self.interest_rate = rate.
D.Define balance as a class attribute in Account with a default value and remove it from __init__.
AnswerC

Calling super().__init__(account_number, balance) immediately at the start of SavingsAccount.__init__ delegates construction of base-class attributes to Account, preserving its invariants and any additional logic it performs. After the delegate call, assigning self.interest_rate = rate adds the subclass-specific attribute. This is the canonical cooperative-inheritance pattern and ensures the object is fully initialized before it is used.

Why this answer

In Python, the subclass SavingsAccount overrides __init__ but never invokes the parent's initializer, so account_number and balance are never set on the instance. Calling super().__init__(account_number, balance) as the first line of SavingsAccount.__init__ runs Account's initializer, which assigns those attributes, and then the subclass can safely set self.interest_rate = rate. This is the canonical cooperative-initialization pattern and avoids duplicating the parent's assignment logic.

Exam trap

The trap is that candidates see 'no code duplication' and jump to option B (manually assign the attributes) because it looks explicit and safe, missing that the question is testing the cooperative super().__init__ pattern as the idiomatic, non-duplicating fix.

How to eliminate wrong answers

Option A is wrong because removing SavingsAccount.__init__ would drop the interest_rate assignment entirely, and the default Account.__init__ signature would not accept the third positional argument. Option B is wrong because it duplicates the parent's attribute-assignment logic in the subclass, which is exactly the code duplication the question asks to avoid and breaks if Account's initialization changes. Option D is wrong because moving balance to a class attribute changes its semantics — it would be shared across instances and no longer set per-instance from the constructor argument.

55
MCQmedium

A team wants a `Logger` class that can be used both as `Logger.log('msg')` on the class itself and as `logger.log('msg')` on an instance, with identical behaviour and no access to instance state. Which decorator should be applied to `log`?

A.`@staticmethod`
B.`@classmethod`
C.`@abstractmethod`
D.`@property`
AnswerA

A static method receives no implicit first argument, so `Logger.log('msg')` and `logger.log('msg')` both call the same function with identical arguments and identical results. Since the method does not need instance state, the absence of `self` is not a problem, and there is no unused `cls` parameter to confuse callers. This exactly matches the requirement of identical behaviour from both the class and an instance.

Why this answer

A static method is bound to neither the instance nor the class, so it can be invoked through either the class name or an instance with the same argument list and the same result. Because the logging behaviour does not depend on instance or class state, the missing implicit parameter is not a limitation. A class method would work but needlessly injects the class as an argument, and the other decorators change attribute semantics rather than call binding.

Exam trap

The trap here is choosing a class method because it is callable from both the class and instances, overlooking that it injects an unused class argument the scenario does not need.

56
MCQhard

An abstract base class (ABC) defines an abstract method `process()`. Several subclasses implement it. A function accepts any subclass and calls `process()`. This demonstrates which OOP concept?

A.Inheritance.
B.Encapsulation.
C.Method overloading.
D.Polymorphism.
AnswerD

Polymorphism allows objects of different classes that share a common abstract base to be used interchangeably, with the correct override of `pro` invoked dynamically at runtime. Because `pro` is abstract, each concrete subclass must provide its own implementation, and the Python runtime dispatches to the specific version based on the actual object type. This is precisely the concept the question is testing.

Why this answer

Polymorphism allows objects of different subclasses to be treated uniformly through their common interface. When a function accepts any subclass of the ABC and calls `process()`, the correct implementation is resolved at runtime via dynamic dispatch, which is the essence of polymorphism in Python.

Exam trap

Python Institute often tests the distinction between inheritance and polymorphism by presenting a scenario where multiple subclasses override a method, leading candidates to mistakenly select 'inheritance' because they see the class hierarchy, but the key behavior is the polymorphic call, not the inheritance itself.

How to eliminate wrong answers

Option A is wrong because inheritance alone only establishes a parent-child relationship and code reuse; it does not guarantee that the same method call behaves differently across subclasses. Option B is wrong because encapsulation is about bundling data and methods together and restricting direct access to internal state, which is not demonstrated by calling a method on different subclass objects. Option C is wrong because method overloading refers to multiple methods with the same name but different parameters within the same class, which Python does not support natively; the scenario here uses a single method signature overridden in subclasses, not overloading.

57
MCQmedium

An application uses a class to represent a configuration object that reads settings from a file. The class has a class attribute config_cache that holds a dictionary of loaded configurations to avoid repeated file reads. However, the developer notices that when they modify the dictionary for one instance, it affects all instances. They want to ensure that each instance has its own copy of the configuration data upon initialization. Which change should they make?

A.Move the dictionary initialization into the __init__ method so each instance creates its own dictionary.
B.Use a @staticmethod to return a new dictionary each time.
C.Keep the class attribute but use a deep copy in __init__ before modifying.
D.Define the dictionary inside a class method.
AnswerA

Class attributes are shared across all instances, so mutating the cached dictionary propagates everywhere. Defining it inside __init__ binds a fresh dictionary to each instance's namespace, satisfying the requirement that every instance holds its own independent configuration copy.

Why this answer

Moving the dictionary initialization into the __init__ method ensures that each instance gets its own separate dictionary object. Class attributes are shared across all instances, so modifying the dictionary via one instance changes it for all. By assigning `self.config_cache = {}` inside __init__, each instance creates a new, independent dictionary upon instantiation, solving the shared-state problem.

Exam trap

Python Institute often tests the distinction between mutable and immutable class attributes, trapping candidates who think a deep copy in __init__ will fix the sharing issue, when in fact the shared reference to the class attribute itself must be replaced with an instance attribute.

How to eliminate wrong answers

Option B is wrong because a @staticmethod that returns a new dictionary would still need to be called and assigned to an instance attribute; if the result is stored in a class attribute, the sharing issue persists. Option C is wrong because using a deep copy in __init__ before modifying does not prevent the initial shared reference; the class attribute itself remains a single dictionary that all instances point to, so any modification to the original (or a copy made later) still affects the shared object. Option D is wrong because defining the dictionary inside a class method does not change its scope; if the method assigns to a class attribute, it remains shared, and if it returns a new dict, the instance must still store it properly to avoid sharing.

58
MCQeasy

A developer wants to ensure that a class attribute is shared among all instances but cannot be modified from outside the class. Which approach is most appropriate?

A.Use a property decorator on a class method
B.Define a public class attribute and document it as read-only
C.Define an instance attribute inside __init__
D.Define a private class attribute (e.g., __shared) and provide a class method to access it
AnswerD

Defining `__shared` inside the class body but outside `__init__` puts it in the class namespace, and Python's name mangling rewrites it to `_ClassName__shared`, which discourages accidental external access. A `@classmethod` getter (for example `cls.__shared`) can return the value to all callers without exposing a writable descriptor; while a determined caller could still use the mangled name, the mangling plus getter signals that the attribute is private and read-only. This achieves shared state across every instance and gives a single controlled access point.

Why this answer

Defining a private class attribute with name mangling (e.g., `__shared`) prevents direct external modification, and providing a class method (using `@classmethod`) allows read-only access to the attribute. This ensures the attribute is shared among all instances (since it belongs to the class, not instances) while enforcing encapsulation.

Exam trap

Python Institute often tests the distinction between class-level and instance-level attributes, and the trap here is that candidates confuse the `@property` decorator (which works on instance methods) with class-level read-only access, or assume documentation alone provides protection.

How to eliminate wrong answers

Option A is wrong because a property decorator is designed for instance attributes, not class attributes; applying it to a class method would not create a shared, read-only class-level attribute. Option B is wrong because documenting a public class attribute as read-only does not enforce immutability — external code can still modify it directly, violating the requirement. Option C is wrong because an instance attribute defined inside `__init__` is unique to each instance, not shared among all instances.

59
MCQmedium

A team is implementing a shape hierarchy with a base class `Shape` that should have an `area()` method. They want to ensure that every subclass must provide its own implementation of `area()`. Which approach should they use?

A.Define `area()` in `Shape` and have it raise `NotImplementedError`.
B.Use a class method that must be overridden.
C.Define `area()` as a property that raises an error.
D.Use `@abstractmethod` from the `abc` module to declare `area()` as abstract.
AnswerD

Decorating `area()` with `@abstractmethod` inside an `ABC` subclass installs `ABCMeta` as the metaclass, which tracks the class's `__abstractmethods__` set. If any abstract method remains unimplemented, `ABCMeta.__call__` refuses to instantiate the class (`TypeError`), and any concrete subclass must override `area()` (or remain abstract itself). This gives construction-time enforcement rather than deferring errors to method calls.

Why this answer

The `abc` module provides the `ABCMeta` metaclass and the `@abstractmethod` decorator, which together enforce that any concrete subclass must override the abstract method. If a subclass fails to implement `area()`, Python raises a `TypeError` at instantiation time, ensuring the design contract is upheld. This is the standard Pythonic way to define abstract base classes and enforce method implementation in subclasses.

Exam trap

Python Institute often tests the distinction between raising `NotImplementedError` (a runtime-only check) and using `@abstractmethod` (which prevents instantiation of incomplete subclasses), leading candidates to mistakenly choose Option A because they think 'raising an error' is sufficient for enforcement.

How to eliminate wrong answers

Option A is wrong because raising `NotImplementedError` at runtime does not enforce compile-time or instantiation-time checks; a subclass can forget to override `area()` and the error will only appear when the method is called, not when the object is created. Option B is wrong because a class method (`@classmethod`) is not designed for abstract method enforcement; it can be overridden but there is no built-in mechanism to require overriding, and the `@abstractmethod` decorator is the correct tool for that purpose. Option C is wrong because defining `area()` as a property that raises an error does not prevent instantiation of a subclass that fails to override the property; the error only occurs when the property is accessed, and properties are not intended for abstract method enforcement.

60
MCQmedium

Consider a class `D` that inherits from multiple base classes `B` and `C`. The developer wants to call a method from a specific parent class while ensuring correct method resolution order (MRO). Which is the safest way?

A.`self.method()`
B.`ParentClass.method(self)`
C.`super().method()`
D.`BaseClass.method(self)`
AnswerC

`super().method()` delegates to the next class in the MRO after the current class, not necessarily the immediate parent, which is exactly what cooperative multiple inheritance requires. Python computes the MRO using the C3 linearization algorithm, so every ancestor appears exactly once and after each class's call to `super()`, the chain continues in the correct order. This ensures shared base classes are processed only once and remains correct even when the hierarchy changes.

Why this answer

In Python, `super().method()` is the safest way to call a method from a parent class in a multiple inheritance scenario because it respects the Method Resolution Order (MRO) defined by the C3 linearization algorithm. This ensures that the method is resolved from the next class in the MRO, avoiding hard-coded references that could break if the inheritance hierarchy changes. It also correctly handles cooperative multiple inheritance, where each class in the MRO can collaborate via `super()` calls.

Exam trap

Python Institute often tests the misconception that `super()` only calls the immediate parent class, when in fact it follows the full MRO, and candidates mistakenly choose a hard-coded parent call (like Option B or D) thinking it is more explicit and safer.

How to eliminate wrong answers

Option A is wrong because `self.method()` will invoke the method on the instance using the MRO, starting from the class of `self`, which may not call the intended parent class method if the method is overridden in a subclass. Option B is wrong because `ParentClass.method(self)` is a hard-coded reference that bypasses the MRO entirely, leading to potential issues in diamond inheritance or if the class hierarchy is modified. Option D is wrong because `BaseClass.method(self)` is essentially the same as Option B — it directly calls a specific base class method, ignoring the MRO and breaking cooperative multiple inheritance patterns.

61
MCQmedium

A programmer writes a class with a static method using @staticmethod. What is the primary purpose of using a static method instead of a class method or instance method?

A.To access class variables without creating an instance
B.To define a method that does not depend on class or instance state and behaves like a regular function but is namespaced inside the class
C.To allow the method to be overridden in subclasses
D.To enforce that the method cannot be called from an instance
AnswerB

A staticmethod is a method that has no dependency on either the class or an instance: Python does not pass an implicit first argument, so the function signature stays exactly as written. It behaves like a plain module-level function but is namespaced inside the class to keep related utilities organized. This makes it ideal for helper logic that doesn't need or receive class/instance context, such as input validation or format conversion, while still being callable from both the class and instances.

Why this answer

A static method in Python, decorated with @staticmethod, does not receive an implicit first argument (neither self nor cls). This means it cannot access or modify class or instance state; it behaves exactly like a regular function but is organized within the class's namespace for logical grouping. The primary purpose is to encapsulate utility functions that are related to the class but do not depend on its data.

Exam trap

Python Institute often tests the distinction between static and class methods by making candidates think that @staticmethod is used to access class variables, when in fact that is the role of @classmethod, and the trap is that both decorators avoid the need for an instance, but only @classmethod receives the class reference.

How to eliminate wrong answers

Option A is wrong because accessing class variables without an instance is the purpose of a class method (decorated with @classmethod), which receives the class as the first argument (cls) and can read or write class-level attributes; a static method has no access to cls and cannot directly access class variables unless they are passed explicitly. Option C is wrong because static methods are not overridden in subclasses in the same way as instance or class methods; they are resolved at compile time (early binding) and do not participate in the normal method resolution order (MRO) for inheritance, so overriding them has no effect when called on the subclass. Option D is wrong because static methods can be called from an instance just fine; Python allows calling any method from an instance, and @staticmethod does not restrict this — the decorator only removes the implicit self parameter, not the ability to invoke it on an object.

62
MCQhard

A developer writes a class `Vector` and wants `v1 + v2` to return a brand-new `Vector`, while `v1 += v2` should mutate `v1` in place and return it. Which pair of special methods achieves this behaviour?

A.`__add__` returning a new `Vector`, and `__radd__` mutating `self` and returning `self`.
B.`__add__` mutating `self` and returning `self`, and `__radd__` returning a new `Vector`.
C.`__add__` returning a new `Vector`, and `__iadd__` mutating `self` and returning `self`.
D.`__iadd__` returning a new `Vector`, and `__add__` mutating `self` and returning `None`.
AnswerC

`__add__` is invoked for the binary `+` operator and conventionally returns a new object, which satisfies the first requirement. `__iadd__`, when defined, is called by the augmented assignment `+=`; mutating the instance and returning it makes `v1 += v2` update the existing object rather than rebinding the name to a new one. Together they produce exactly the two distinct behaviours requested.

Why this answer

Defining `__add__` for the binary plus operator and `__iadd__` for augmented assignment is the idiomatic way to separate non-mutating and in-place semantics. The binary method returns a fresh instance so operands are left untouched, while the in-place method mutates the receiver and returns it so the same object identity is preserved after `+=`. Reflected methods such as `__radd__` serve a different purpose and cannot substitute for either.

Exam trap

The trap here is believing `__radd__` participates in `v1 += v2`, when it only handles reflected operations where the left operand cannot perform the addition.

63
MCQhard

A developer is working on a class hierarchy for geometric shapes. They have a base class Shape with an abstract method area(). They also have a mixin class Drawable that provides a method draw(). They want to create a class Rectangle that inherits from both Shape and Drawable. However, they encounter a TypeError when trying to instantiate Rectangle because the abstract method area() is not implemented. Which action should they take to resolve this?

A.Change the inheritance order to Drawable first, then Shape.
B.Implement the area() method in Rectangle.
C.Use a class decorator @abstractmethod for Rectangle.
D.Remove the abstract method from Shape by removing the @abstractmethod decorator.
AnswerB

Implementing area() in Rectangle satisfies the abstract-method contract inherited from Shape, so instantiation no longer raises TypeError. Python's ABCMeta blocks instantiation while any abstract method remains unimplemented; providing a concrete override in the subclass clears that check. Multiple inheritance from Drawable is unaffected, since mixins impose no abstract requirements here.

Why this answer

The abstract method area() declared in the Shape base class must be implemented in any concrete subclass. In Python, a class that inherits from an ABC (Abstract Base Class) with an abstract method cannot be instantiated until that method is overridden. By providing an implementation of area() in Rectangle, the class becomes concrete and can be instantiated without raising a TypeError.

Exam trap

Python Institute often tests the misconception that changing inheritance order or using decorators can bypass the abstract method requirement, when in fact the only valid fix is to implement the abstract method in the concrete subclass.

How to eliminate wrong answers

Option A is wrong because changing the inheritance order does not affect the requirement to implement abstract methods; the TypeError arises from the missing implementation, not from the MRO. Option C is wrong because @abstractmethod is a decorator used to declare abstract methods, not a class decorator; applying it to Rectangle would make Rectangle itself abstract, not resolve the missing implementation. Option D is wrong because removing the @abstractmethod decorator from Shape would break the design contract, but more importantly, the question asks how to resolve the error while preserving the abstraction; removing the decorator eliminates the requirement but is not the intended solution for a proper class hierarchy.

64
Multi-Selectmedium

Which THREE of the following are true about the `__init__` method in Python?

Select 3 answers
A.It can be called manually.
B.It must return a value.
C.It can accept arguments.
D.It is not inherited.
E.It is called automatically when an instance is created.
AnswersA, C, E

Although __init__ is invoked automatically during instance creation, it is also an ordinary method and can be called manually on an existing instance, such as obj.__init__(new_args). This manually calls the initializer to reset or reinitialize the object's attributes, which can be useful for object reuse or unit testing. The ability to call it manually does not interfere with its automatic invocation; both can coexist. Thus, the statement is true.

Why this answer

The __init__ method is a special method in Python classes used for initializing newly created objects. It can be called manually (e.g., obj.__init__()), it accepts arguments that are passed during instance creation, and it is automatically invoked when an instance is created via the class constructor. It does not require a return value (it returns None), and it is inherited by subclasses unless explicitly overridden.

Exam trap

A common trap is thinking that __init__ cannot be called manually or that it is the constructor (it is an initializer; __new__ is the constructor). Also, some believe __init__ must return a value, but it must return None.

65
MCQhard

A developer defines a class 'A' with a method 'm' that uses 'self.a'. Class 'B' inherits from 'A' and defines __init__ that sets 'self.a = 10'. An instance of B is created and method m is called. What is the output?

A.None
B.AttributeError
C.10
D.0
AnswerC

The method m, inherited from A, executes with self bound to a B instance. During instantiation, B.__init__ executes self.a = 10, placing a in the instance's __dict__. Consequently, the expression inside m that uses a evaluates to 10, matching the assigned value exactly.

Why this answer

When an instance of class B is created, its __init__ method sets self.a = 10. When method m (inherited from A) is called on that instance, self refers to the B instance, so self.a resolves to 10. Python's attribute lookup follows the instance's __dict__ first, finding the attribute set by B's __init__.

Exam trap

Python Institute often tests the misconception that inherited methods use the parent class's attribute values rather than the instance's own attributes, leading candidates to incorrectly expect an AttributeError or None.

How to eliminate wrong answers

Option A is wrong because self.a is not None; it is explicitly set to 10 in B.__init__, so the output is not None. Option B is wrong because there is no AttributeError; the attribute a exists on the instance due to B.__init__, so the lookup succeeds. Option D is wrong because self.a is not 0; it is assigned the integer 10, not a default or zero value.

66
MCQmedium

A class `Vector` defines `__add__` to return a new `Vector`. A developer writes `v1 = Vector(1,2); v2 = Vector(3,4); v3 = v1 + v2`. What is the type of `v3`?

A.`None`
B.`Vector`
C.`int`
D.`tuple`
AnswerB

When `v1 + v2` is evaluated, Python calls `v1.__add__(v2)`. If `__add__` is implemented to return a new `Vector` instance, then `v3` will be of type `Vector`. This is the expected behavior for operator overloading in Python, allowing custom objects to support arithmetic operations.

Why this answer

Operator overloading in Python allows classes to define behavior for operators like `+`. The `__add__` method is called on the left operand, and its return value becomes the result of the expression. Since the `Vector` class implements `__add__` to return a new `Vector`, `v3` is a `Vector` instance.

Exam trap

The trap here is assuming that `+` always returns a numeric type, but with operator overloading, it can return any type.

67
MCQhard

When a class defines both __getattr__ and __getattribute__, which one is called when accessing an attribute that exists in the instance?

A.__getattr__ always overrides __getattribute__.
B.Both __getattr__ and __getattribute__ are called, in that order.
C.Only __getattr__ is called.
D.__getattribute__ is called, and __getattr__ is not called.
AnswerD

For a normal attribute that exists on the instance or class, __getattribute__ is invoked and successfully returns the value, so Python never proceeds to the __getattr__ fallback. This is the correct behavior when a class defines both hooks: the primary lookup method handles the access, and __getattr__ is reserved for cases where the attribute is genuinely missing.

Why this answer

In Python, when both __getattr__ and __getattribute__ are defined in a class, __getattribute__ is always called first for every attribute access. If the attribute exists in the instance (e.g., in the instance dictionary or via a descriptor), __getattribute__ returns it directly, and __getattr__ is never invoked. __getattr__ is only called as a fallback when __getattribute__ raises an AttributeError. Therefore, for an existing attribute, only __getattribute__ runs, making option D correct.

Exam trap

Python Institute often tests the misconception that __getattr__ is the primary hook for attribute access, when in fact __getattribute__ is always called first and __getattr__ is only a fallback for missing attributes.

How to eliminate wrong answers

Option A is wrong because __getattr__ does not override __getattribute__; rather, __getattribute__ takes precedence for all accesses, and __getattr__ is only a fallback for missing attributes. Option B is wrong because both methods are not called in order for an existing attribute; __getattribute__ returns the value immediately, so __getattr__ is not invoked at all. Option C is wrong because __getattr__ is not called when the attribute exists; it is only triggered when __getattribute__ raises an AttributeError.

68
MCQeasy

A class MyClass defines __str__ and __repr__ methods. What is the purpose of __repr__?

A.To return a human-readable string for end users
B.To compare object equality
C.To return a hash value of the object
D.To return an unambiguous representation of the object, ideally for debugging
AnswerD

The canonical purpose of __repr__ is to return an unambiguous, developer-oriented string that is ideally a valid Python expression such that eval(repr(obj)) recreates the object. This makes it the primary tool for debugging and logging, where the exact type and state of an object matter more than polished presentation. It intentionally favors completeness and precision over the informal readability that __str__ provides.

Why this answer

The `__repr__` method is designed to return an unambiguous string representation of an object, ideally one that can be used to recreate the object or that clearly shows its internal state for debugging purposes. This is distinct from `__str__`, which targets end-user readability. The Python documentation explicitly states that `__repr__` should be unambiguous, while `__str__` should be readable.

Exam trap

The trap here is that candidates confuse `__repr__` with `__str__`, assuming both serve the same purpose, but The PCAP exam specifically tests that `__repr__` is for unambiguous debugging output, not user-friendly display.

How to eliminate wrong answers

Option A is wrong because returning a human-readable string for end users is the purpose of `__str__`, not `__repr__`. Option B is wrong because comparing object equality is handled by the `__eq__` method, not `__repr__`. Option C is wrong because returning a hash value is the job of the `__hash__` method, which is used for dictionary keys and set membership, not `__repr__`.

69
MCQeasy

A developer wants to implement a read-only property for a class 'Temperature' that returns the temperature in Celsius but prevents external modification. Which code snippet correctly defines such a property?

A.class Temperature: @property def celsius(self): return self._celsius @celsius.setter def celsius(self, val): self._celsius = val
B.class Temperature: def __setattr__(self, name, val): if name == 'celsius': raise AttributeError
C.class Temperature: @property def celsius(self): return self._celsius
D.class Temperature: def __init__(self, c): self.celsius = c
AnswerC

This is the correct read-only property because it defines only a getter method via `@property`. With no setter, Python treats the property as read-only, and assigning to `celsius` raises `AttributeError`. Internal storage in `self._celsius` remains private, while the property exposes a controlled public interface.

Why this answer

It defines a read-only property using the `@property` decorator with only a getter method. Without a setter, any attempt to assign a value to `celsius` will raise an `AttributeError`, making the property read-only. This is the standard Pythonic way to implement a read-only attribute.

Exam trap

Python Institute often tests the misconception that a property without a setter is still writable by default, or that overriding `__setattr__` is a valid alternative to `@property` for creating a read-only attribute, but the correct approach is to omit the setter decorator entirely.

How to eliminate wrong answers

Option A is wrong because it includes a setter method (`@celsius.setter`), which allows external modification of the property, contradicting the requirement for a read-only property. Option B is wrong because overriding `__setattr__` to raise an `AttributeError` for the name 'celsius' would prevent setting the attribute even inside the class (e.g., in `__init__`), breaking normal initialization and not providing a proper property interface. Option D is wrong because it simply assigns `celsius` as a regular instance attribute in `__init__`, which is fully writable and does not use the `@property` decorator, so it is not a read-only property.

70
MCQmedium

A team is developing a system that must handle different types of documents (PDF, Word, etc.). Each document type has a unique parsing method. To avoid massive conditional logic, which OOP concept should be applied?

A.Polymorphism
B.Encapsulation
C.Inheritance
D.Abstraction
AnswerA

Polymorphism (specifically subtype polymorphism) is correct because it allows a common interface or base-class reference to invoke different implementations depending on the actual runtime type. In a system handling different types, a single method call can be dispatched dynamically to the appropriate subclass override, which directly addresses the need for type-specific behavior. This is the OOP feature specifically designed to let objects of different classes respond to the same message in distinct ways.

Why this answer

Polymorphism allows different document types (PDF, Word, etc.) to be treated uniformly through a common interface (e.g., a `parse()` method) while each class implements its own parsing logic. This eliminates the need for conditional statements (like `if type == 'PDF'`) because the correct method is resolved at runtime via dynamic dispatch, which is exactly what the team needs to avoid massive conditional logic.

Exam trap

Python Institute often tests the distinction between inheritance and polymorphism: candidates mistakenly think inheritance alone solves the problem, but without polymorphic method dispatch, you still need conditional logic to handle different types.

How to eliminate wrong answers

Option B (Encapsulation) is wrong because encapsulation focuses on bundling data and methods together and restricting direct access to internal state (e.g., via private attributes), not on avoiding conditional logic for different types. Option C (Inheritance) is wrong because while inheritance can share common code among document types, it does not by itself eliminate the need for conditionals; you would still need to check the object's type to call the correct parsing method without polymorphism. Option D (Abstraction) is wrong because abstraction hides implementation details behind an interface or abstract class, but without polymorphism you would still need conditional logic to decide which concrete implementation to invoke.

71
MCQmedium

Refer to the exhibit. Which of the following correctly shows the MRO of class D?

A.[D, B, C, A, object]
B.[D, A, B, C, object]
C.[D, B, A, C, object]
D.[D, C, B, A, object]
AnswerA

This is the exact output of C3 linearization for a diamond hierarchy where D inherits from B and C, and both B and C inherit from A. The merge step first takes D, then B because it is the head of the first base list and appears in no other list's tail, then C (not A, because A is in the tail of C's list), and finally A and object. This preserves both local precedence (B before C) and monotonicity (each base's own MRO is a subsequence of D's).

Why this answer

Python's Method Resolution Order (MRO) for class D, which inherits from B and C (where B inherits from A and C inherits from A), follows the C3 linearization algorithm. The MRO is computed as D -> B -> C -> A -> object, ensuring that each class appears before its parents and that the order respects the local precedence order of D's bases (B before C).

Exam trap

Python Institute often tests the C3 linearization rule that the local precedence order (the order of bases in the class definition) must be preserved, so candidates mistakenly reorder bases based on inheritance depth rather than the explicit left-to-right order in the class statement.

How to eliminate wrong answers

Option B is wrong because it places A before B and C, violating the local precedence order of D's bases (B, C) and the C3 algorithm's requirement that a parent class appears after all its subclasses. Option C is wrong because it places A before C, which breaks the monotonicity of the C3 linearization; since C inherits from A, A must come after C. Option D is wrong because it places C before B, ignoring the explicit order of bases in class D's definition (B then C), which the C3 algorithm respects as the local precedence order.

72
Multi-Selecthard

Which TWO statements about Python's name mangling are correct?

Select 2 answers
A.Name mangling applies to all method names that start with a single underscore.
B.The mangled name format is _ClassName__attribute.
C.Name mangling prevents external code from accessing the attribute entirely.
D.Name mangling is applied to attributes that start with two underscores but do not end with two underscores.
E.Name mangling occurs at runtime.
AnswersB, D

When an identifier with two leading underscores appears inside a class body, the compiler rewrites it by prefixing a single underscore and the class name: __attr becomes _ClassName__attr. This renaming applies to every lexical occurrence of that identifier within the class definition, so methods that reference the attribute are also rewritten. This is exactly why the mangled format is _ClassName__attribute.

Why this answer

Python's name mangling transforms an attribute name like `__attribute` defined in a class `MyClass` into `_MyClass__attribute`. This mechanism is specifically designed to avoid name clashes in subclasses, not to enforce privacy. The transformation is done by the compiler at definition time, not at runtime.

Exam trap

Python Institute often tests the misconception that name mangling provides true access control (like private in Java), when in fact it is only a name transformation that can be bypassed by using the mangled name directly.

73
MCQhard

A Python class 'Shape' defines an abstract method 'area'. Subclasses 'Circle' and 'Square' implement 'area'. A function 'calculate_area(shape)' expects a 'Shape' instance. Which principle ensures that the function works correctly without knowing the specific subclass?

A.Interface Segregation Principle
B.Liskov Substitution Principle
C.Single Responsibility Principle
D.Dependency Inversion Principle
AnswerB

The Liskov Substitution Principle (LSP) asserts that any subclass must be able to replace its base class without altering the correctness of the program. When Shape declares an abstract area() method, it establishes a behavioral contract that every subclass must honor; if a subclass overrides area() with an incompatible return type, raises an unexpected exception, or changes invariants, it breaks substitutability. This is exactly the design concern addressed by LSP, making it the correct principle for the described scenario.

Why this answer

The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In this scenario, 'calculate_area(shape)' accepts a 'Shape' instance, and because both 'Circle' and 'Square' are proper subtypes that honor the contract of the 'area' method, the function works correctly regardless of which subclass is passed. This is the core of LSP: substitutability without side effects.

Exam trap

Python Institute often tests LSP by presenting a scenario where a subclass overrides a method in a way that changes the expected behavior (e.g., raising an exception or returning a different type), and candidates mistakenly choose Interface Segregation or Dependency Inversion because they confuse 'substitutability' with 'abstraction' or 'interface design'.

How to eliminate wrong answers

Option A is wrong because the Interface Segregation Principle (ISP) focuses on splitting large interfaces into smaller, specific ones so that clients only depend on methods they use; it does not address the substitutability of subclasses in a function parameter. Option C is wrong because the Single Responsibility Principle (SRP) dictates that a class should have only one reason to change, which is unrelated to polymorphic behavior across subclasses. Option D is wrong because the Dependency Inversion Principle (DIP) deals with depending on abstractions rather than concretions, but it does not specifically ensure that a subclass can be used in place of its parent class without breaking functionality—that is LSP's role.

74
MCQeasy

A developer defines a class with a private attribute `_value` and wants to provide controlled access. Which approach is the most Pythonic?

A.Use `__slots__` to restrict attribute creation.
B.Make `_value` public and rely on documentation.
C.Use @property to define getter and setter methods.
D.Define `get_value()` and `set_value()` methods.
AnswerC

Using the `@property` decorator is the canonical Pythonic way to encapsulate private attributes because it allows you to define getter and setter methods while preserving plain attribute syntax for callers. The getter can hide internal representation or perform lazy computation, and the setter can validate input before assigning to the underlying `_value`, ensuring invariants hold. This provides controlled access without forcing explicit method calls, striking the right balance between encapsulation and usability.

Why this answer

Using the `@property` decorator is the most Pythonic way to implement controlled access to a private attribute. It allows you to define getter and setter methods that can be called like regular attribute access (e.g., `obj.value`), preserving encapsulation while maintaining a clean, non-method-call interface. This approach aligns with Python's philosophy of 'we are all consenting adults' and avoids the verbosity of explicit getter/setter methods.

Exam trap

Python Institute often tests the distinction between Pythonic idioms and patterns borrowed from other languages (like Java), so the trap here is that candidates familiar with Java or C++ may choose Option D (explicit getter/setter methods) because it looks familiar, missing that Python's `@property` is the preferred, more concise approach.

How to eliminate wrong answers

Option A is wrong because `__slots__` is used to restrict the attributes that can be assigned to an instance, not to provide controlled access to a specific attribute; it does not define getters or setters. Option B is wrong because making `_value` public and relying on documentation violates encapsulation principles and provides no runtime enforcement or validation, which is not Pythonic for controlled access. Option D is wrong because defining `get_value()` and `set_value()` methods is a Java-style approach that is considered non-Pythonic; Python prefers the `@property` decorator for a more natural attribute-like syntax.

75
Multi-Selecthard

Which THREE of the following are characteristics of Python's special methods (dunder methods)? (Select exactly three.)

Select 3 answers
A.They enable operator overloading for user-defined classes.
B.They can be defined in a class to customize behavior.
C.Every class automatically has all special methods predefined.
D.They must be defined inside the class definition and cannot be added later.
E.They are automatically invoked by Python in certain contexts.
AnswersA, B, E

Special methods allow user-defined classes to redefine how operators such as +, -, ==, and < behave by implementing reserved dunder names like __add__, __eq__, or __lt__. When Python evaluates an expression like a + b, it dispatches to a.__add__(b) (or the reflected method on b if needed), enabling custom types to support the same syntactic sugar as built-in types. For example, a Vector class can define __add__ to perform element-wise addition, making v1 + v2 valid.

Why this answer

Special methods like `__add__` and `__eq__` allow user-defined classes to redefine the behavior of operators such as `+` and `==`. When Python encounters an operator expression, it looks up the corresponding dunder method on the object's class, enabling operator overloading in a clean, syntactic way.

Exam trap

Python Institute often tests the misconception that all dunder methods are automatically inherited or that they cannot be added dynamically, so candidates mistakenly select option C or D without realizing that Python's data model only provides defaults for a minimal set and allows runtime assignment to classes.

Page 1 of 2 · 109 questions totalNext →

Ready to test yourself?

Try a timed practice session using only Object-Oriented Programming questions.