Courseiva

CCNA Oop Questions

51 questions · Oop topic · All types, answers revealed

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

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

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

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

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

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

7
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

This method is invoked for every attribute access, making it suitable for logging.

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.

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

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

10
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

That's the defining difference.

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.

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

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

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

14
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

Each class has a __mro__ attribute showing the order.

Why this answer

Every Python class has an `__mro__` attribute that returns a tuple of classes in the order they are searched for methods and attributes. This attribute is automatically generated by the C3 linearization algorithm and provides a clear, inspectable view of the resolution order.

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.

15
MCQmedium

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

A.AttributeError
B.0
C.None
D.100
AnswerA

Name mangling prevents direct access to __balance.

Why this answer

The code attempts to access the attribute `balance` on an instance of `BankAccount`, but the class uses `__balance` (double underscore) in the `__init__` method, which triggers Python's name mangling. The attribute is actually stored as `_BankAccount__balance`. Therefore, `balance` does not exist on the instance, and accessing it raises an `AttributeError`.

In Python, accessing a non-existent attribute raises an error rather than returning a default value.

Exam trap

Python Institute often tests the misconception that accessing a missing attribute in Python returns a default value like `None` or `0`, when in fact it raises an `AttributeError` unless the class defines `__getattr__` or `__getattribute__`.

How to eliminate wrong answers

Option B is wrong because it suggests the output is `0`, which would only happen if `balance` were explicitly initialized to `0` in `__init__` (e.g., `self.balance = 0`), but no such assignment exists. Option C is wrong because `None` would be returned only if `balance` were a method that returns nothing, or if the attribute existed and was set to `None`; here the attribute does not exist at all. Option D is wrong because `100` would require `balance` to be set to `100` somewhere, such as via `self.balance = 100` in `__init__` or after a deposit, but the code never creates or assigns a `balance` attribute.

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

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

18
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 clearly signals the method must be overridden.

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.

19
MCQhard

Refer to the exhibit. Which statement about the output is true?

A.a.__class__.__bases__ returns an empty tuple
B.type(a).__bases__ returns (<class 'object'>,)
C.a.__class__.__bases__ returns (<class 'object'>,)
D.Both print statements output the same thing
AnswerC

Correct: a.__class__ is class A, and A.__bases__ is (object,).

Why this answer

`a.__class__` returns the class of instance `a`, which is `A`. Since `A` does not explicitly inherit from any class, it implicitly inherits from `object`. Therefore, `A.__bases__` returns `(<class 'object'>,)`, which is exactly what `a.__class__.__bases__` evaluates to.

Exam trap

The trap here is that candidates often confuse `a.__class__` with `type(a)` and assume they always return the same thing, but the PCAP exam tests the subtle difference that `__class__` is an attribute of the instance while `type()` is a built-in function, and for instances of classes that override `__class__`, they can differ, leading to different `__bases__` results.

How to eliminate wrong answers

Option A is wrong because `a.__class__.__bases__` does not return an empty tuple; it returns `(<class 'object'>,)` since every class in Python 3 implicitly inherits from `object`. Option B is wrong because `type(a).__bases__` is equivalent to `A.__bases__`, which returns `(<class 'object'>,)`, not `(<class 'object'>,)` — wait, that is actually the same tuple; the error is that the option incorrectly states the return value as `(<class 'object'>,)` when it should be `(<class 'object'>,)` — but the real mistake is that `type(a).__bases__` does not return `(<class 'object'>,)`? Actually, it does; the trap is that `type(a)` returns `<class 'A'>`, and `A.__bases__` is indeed `(<class 'object'>,)`, so Option B is factually correct in value but the question expects the attribute access via `a.__class__` to be the correct form, making B a distractor because it uses `type(a)` instead of `a.__class__`. Option D is wrong because the two print statements output the same thing only if `a.__class__` and `type(a)` return the same class object, which they do for a normal instance, but the question's exhibit likely shows that `a.__class__` and `type(a)` are identical, so the outputs are the same — however, the statement 'Both print statements output the same thing' is false in the context of the exhibit because the exhibit shows different outputs? Actually, the exhibit is not provided, but the correct answer is C, implying that the two print statements do NOT output the same thing, likely because one accesses `__bases__` on the class and the other on the type, but they are the same; the trap is that the exhibit might show a subtle difference, so D is wrong because the outputs are not identical.

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

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

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

23
MCQhard

You are designing a class that should behave like a sequence and support slicing. Which special methods must be implemented?

A.__len__ and __contains__
B.__iter__ and __next__
C.__getitem__ alone
D.__getitem__ and __len__
E.__getitem__ and __setitem__
AnswerD

These two methods are the minimum for a sequence that supports slicing.

Why this answer

For a class to support slicing in Python, it must implement both `__getitem__` (to handle indexing and slice objects) and `__len__` (to define the sequence length, which is required for proper slice boundary handling). Together, these satisfy the sequence protocol, enabling Python's slicing syntax like `obj[start:stop:step]`.

Exam trap

Python Institute often tests the misconception that `__getitem__` alone is enough for slicing, but the trap is that `__len__` is also required for the interpreter to handle slice defaults and negative indices correctly.

How to eliminate wrong answers

Option A is wrong because `__len__` and `__contains__` are not sufficient for slicing; `__contains__` only supports the `in` operator, not indexing or slicing. Option B is wrong because `__iter__` and `__next__` make an object iterable but do not provide indexed access or slicing capabilities. Option C is wrong because `__getitem__` alone can handle basic indexing, but without `__len__`, Python cannot properly compute slice defaults (e.g., `None` for start/stop) or support negative indices in slicing.

Option E is wrong because `__setitem__` is for item assignment, not required for read-only slicing; the sequence protocol for slicing only mandates `__getitem__` and `__len__`.

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

25
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

Proper ABC with abstractmethod.

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.

26
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

This is the instance initializer method.

Why this answer

`__init__` is a predefined special method in Python used as a constructor to initialize an object's state when an instance of a class is created. It is automatically invoked by the Python runtime upon object instantiation, making it a core part of the object-oriented programming model in Python.

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.

27
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

Correct call order via MRO.

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

28
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

Initializing in __init__ creates a new dictionary per instance, avoiding sharing.

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.

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

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

31
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

All abstract methods must be implemented in a concrete subclass.

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.

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

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

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

35
MCQhard

A class has a class attribute that is a list. A developer modifies this list via one instance, and the change is reflected in all other instances. What is the best practice to avoid this unintended sharing?

A.Use a tuple instead of a list.
B.Initialize the list in `__init__` rather than as a class attribute.
C.Use a class method to modify the list.
D.Use `deepcopy` when accessing the list.
AnswerB

Moving the list into `__init__` as `self.items = ...` causes a brand-new list to be created each time an instance is constructed. Because each object gets its own independent list bound to the instance, changes made through one instance never affect another. This is the standard Python pattern for per-instance mutable state and directly eliminates the accidental sharing caused by a class attribute.

Why this answer

Class attributes are shared across all instances. By initializing the list inside `__init__`, each instance gets its own independent list object, preventing unintended mutation from affecting other instances. This is the standard Python pattern for instance-specific mutable data.

Exam trap

Python Institute often tests the distinction between class-level and instance-level attributes, and the trap here is that candidates mistakenly think using a tuple (immutable) or a class method solves the sharing problem, when the real issue is the location of the mutable object's definition.

How to eliminate wrong answers

Option A is wrong because using a tuple prevents mutation entirely, which is not a solution for the requirement to modify the list; it changes the data structure's semantics and would cause an AttributeError on attempted modification. Option C is wrong because a class method still operates on the class-level list, so modifying it via one instance would still affect all instances; the sharing issue is not about the method type but about where the list is stored. Option D is wrong because `deepcopy` only creates a copy at the time of access, but the underlying class attribute remains shared; repeated accesses would require manual copying each time, which is inefficient and does not solve the fundamental design problem.

36
MCQhard

Which of these is NOT a characteristic of Python's descriptor protocol?

A.A descriptor can be used to create properties with custom behavior
B.Descriptors are only used for attributes that are read-only
C.Class variables assigned to a descriptor are automatically intercepted
D.A descriptor must implement __get__
AnswerB

Descriptors can also handle writes and deletes.

Why this answer

Descriptors are not limited to read-only attributes; they can control get, set, and delete operations. A descriptor that only implements `__get__` is a non-data descriptor, which can be overridden by instance attributes, while a data descriptor implements both `__get__` and `__set__` (and optionally `__delete__`), allowing full read-write control. The statement that descriptors are only for read-only attributes is false, as they are commonly used for computed properties, validation, and lazy evaluation.

Exam trap

Python Institute often tests the misconception that descriptors are only for read-only attributes, but the trap here is that descriptors can be read-write (data descriptors) or read-only (non-data descriptors), and the protocol requires at least `__get__`, not that attributes are immutable.

How to eliminate wrong answers

Option A is wrong because descriptors can indeed be used to create properties with custom behavior, such as validation or computed values, via the `__get__`, `__set__`, and `__delete__` methods. Option C is wrong because class variables assigned to a descriptor are automatically intercepted when accessed on an instance, due to Python's attribute lookup precedence (data descriptors override instance attributes). Option D is wrong because a descriptor must implement `__get__` to be considered a descriptor at all; the protocol requires at least `__get__`, while `__set__` and `__delete__` are optional.

37
MCQhard

Refer to the exhibit. What is the output?

A.1 1
B.0 1
C.1 2
D.2 2
AnswerC

This is correct because the two values are read from different namespaces. Example.attr directly accesses the class attribute, which remains 1 after assignment. In contrast, e.attr = 2 creates an instance attribute named 'attr' that shadows the class attribute during attribute lookup on that instance, so the instance dump returns 2. Therefore the output is exactly 1 2.

Why this answer

'1 2'. When `print(A.x, a.x)` is executed, `A.x` accesses the class attribute `x=1`, and `a.x` accesses the instance attribute `x=2` (set in `__init__`), which shadows the class attribute for that instance. Therefore, the output is '1 2'.

Exam trap

The trap is that candidates may think `self.x = 2` modifies the class attribute for all instances, leading them to choose '2 2', or they may forget the instance attribute shadows the class attribute, choosing '1 1'.

How to eliminate wrong answers

Option A is wrong because '1 1' would only occur if both `a.x` and `A.x` accessed the class attribute, but the instance attribute `self.x = 2` shadows the class attribute for the instance. Option B is wrong because '0 1' would require `x` to be 0 somewhere, which is never assigned. Option D is wrong because '2 2' would require `A.x` to also be 2, but the class attribute `x` remains 1 and is not modified by the instance assignment.

38
MCQmedium

A Python class 'BankAccount' has a method 'withdraw(amount)' that deducts 'amount' from 'self.balance'. A developer writes a subclass 'SavingsAccount' that overrides 'withdraw' to add a penalty if balance drops below minimum. Which design pattern is being used?

A.Composition
B.Aggregation
C.Method overriding
D.Inheritance
AnswerC

The subclass provides a specific implementation of the inherited method.

Why this answer

Method overriding is the mechanism where a subclass provides a specific implementation of a method that is already defined in its superclass. In this scenario, SavingsAccount overrides the withdraw method from BankAccount to add penalty logic, which is the defining characteristic of method overriding in Python.

Exam trap

The trap here is that candidates often confuse inheritance (the relationship) with method overriding (the specific technique), leading them to select 'Inheritance' instead of 'Method overriding' when the question explicitly describes a subclass redefining a parent method.

How to eliminate wrong answers

Option A is wrong because composition is a design pattern where a class contains instances of other classes as members to achieve code reuse, not where a subclass redefines a parent method. Option B is wrong because aggregation is a special form of composition representing a 'has-a' relationship with a weaker ownership lifecycle, not the act of overriding a method. Option D is wrong because inheritance is the broader mechanism that allows SavingsAccount to derive from BankAccount, but the specific pattern described—redefining withdraw in the subclass—is method overriding, not inheritance itself.

39
MCQeasy

A company needs to model different types of employees. They have a base class `Employee` with a method `calculate_pay()`. For hourly employees, pay = hours * rate; for salaried employees, pay = salary. Which design approach is most appropriate?

A.Use a `@staticmethod` inside `Employee` to compute pay based on a type parameter.
B.Define a module-level function that takes an employee object and computes pay.
C.Create subclasses `HourlyEmployee` and `SalariedEmployee` that override `calculate_pay()`.
D.Use a single `Employee` class with conditional statements to differentiate pay types.
AnswerC

This uses inheritance and polymorphism, the standard OOP approach for such scenarios.

Why this answer

It applies polymorphism through method overriding: each subclass (`HourlyEmployee`, `SalariedEmployee`) provides its own implementation of `calculate_pay()`, allowing the calling code to treat all employees uniformly via the base class interface. This adheres to the Open/Closed Principle and keeps the design extensible without modifying existing code when new employee types are added.

Exam trap

Python Institute often tests the distinction between using inheritance with method overriding versus using conditionals or static methods, and the trap here is that candidates may think a single class with `if` statements is simpler and therefore better, missing the long-term maintenance and extensibility advantages of polymorphism.

How to eliminate wrong answers

Option A is wrong because a `@staticmethod` cannot access instance attributes (`hours`, `rate`, `salary`) without passing them explicitly, and using a type parameter violates polymorphism by requiring conditional logic inside the static method. Option B is wrong because a module-level function breaks encapsulation and does not leverage the class hierarchy, making it harder to extend and maintain as employee types grow. Option D is wrong because using conditional statements inside a single `Employee` class violates the Open/Closed Principle and leads to fragile code that must be modified every time a new pay type is introduced.

40
MCQhard

Refer to the exhibit. What is the output and why?

A.AttributeError
B.1
C.2
D.3
AnswerC

Correct. The class attribute `counter` starts at 0 and is incremented by 1 each time an instance is created. Two instances are created, so `A.counter` becomes 2.

Why this answer

The code defines a class `A` with a class attribute `counter` set to 0, and an `__init__` method that increments `A.counter` (the class attribute) by 1 each time an instance is created. Creating two instances (`a1` and `a2`) increments the class attribute twice, so `A.counter` becomes 2. The `print(A.counter)` statement outputs the class attribute value, which is 2.

Exam trap

The trap here is that candidates may mistakenly think `self.counter` is being incremented (creating an instance attribute) rather than `A.counter` (the class attribute), leading them to expect the output to be 1 or to overlook that the class attribute is shared and incremented by each instantiation.

How to eliminate wrong answers

Option A is wrong because there is no AttributeError; the class attribute `counter` is defined and accessed correctly via `A.counter`. Option B is wrong because the output is not 1; creating two instances increments the counter twice, not once. Option D is wrong because the output is not 3; only two instances are created, so the counter is incremented exactly twice, not three times.

41
MCQeasy

A junior developer wrote a class representing a bank account with a private attribute balance. They used double underscore prefix (__balance) to make it private. However, in a test script, they are still able to access the attribute using the mangled name _Account__balance. The developer is confused about why encapsulation is not enforced. Which statement best explains this behavior?

A.The double underscore prefix actually makes the attribute completely inaccessible from outside the class.
B.Python's name mangling is only a convention and does not prevent access.
C.The developer forgot to use the @property decorator.
D.The test script must have used a different class attribute name.
AnswerB

Name mangling is a syntactic transformation, not a security mechanism: `self.__balance` inside a class becomes `self._BankAccount__balance`. It does not prevent external code from accessing the attribute via that mangled name, so it is only a convention. It primarily helps avoid accidental overrides in subclasses rather than hiding data.

Why this answer

Python's name mangling (triggered by a double underscore prefix) is not a security mechanism but a syntactic transformation that renames the attribute to _ClassName__attribute. This prevents accidental name clashes in subclasses but does not enforce true encapsulation; the attribute can still be accessed via the mangled name from outside the class. The developer's confusion stems from mistaking name mangling for a privacy guarantee, which Python intentionally does not provide.

Exam trap

The trap here is that Python Institute often tests the misconception that double underscore prefix enforces true privacy like in languages such as Java or C++, when in reality Python's name mangling is merely a renaming convention that does not prevent access from outside the class.

How to eliminate wrong answers

Option A is wrong because the double underscore prefix does not make the attribute completely inaccessible; it only triggers name mangling, and the attribute remains accessible via the mangled name (e.g., _Account__balance). Option C is wrong because the @property decorator is used to define getter/setter methods for controlled access, but its absence does not affect the ability to access the attribute directly via the mangled name; the core issue is about privacy enforcement, not property decorators. Option D is wrong because the test script correctly uses the mangled name _Account__balance, which is the actual attribute name after mangling; there is no different class attribute name involved.

42
Multi-Selecteasy

Which TWO of the following are valid ways to define a class attribute that is shared by all instances?

Select 2 answers
A.class MyClass: attr: int = 0
B.class MyClass: def set_attr(self): MyClass.attr = 0
C.class MyClass: pass MyClass.attr = 0
D.class MyClass: attr = 0
E.class MyClass: def __init__(self): self.attr = 0
AnswersC, D

Assigns a class attribute after definition.

Why this answer

Assigning an attribute directly to the class after its definition (MyClass.attr = 0) creates a class-level attribute that is shared by all instances. Option D is correct because defining an attribute directly inside the class body (attr = 0) also creates a class-level attribute, accessible to all instances unless shadowed by an instance attribute.

Exam trap

Python Institute often tests the distinction between class attributes and instance attributes, and the trap here is that candidates confuse type annotations (which do not create attributes) with actual assignments, or think that assigning inside a method (like __init__) creates a class-level attribute when it actually creates an instance attribute.

43
MCQmedium

A programmer wants to create a class that cannot be instantiated directly, only through a factory method. Which approach should be used?

A.Raise an exception in __init__ if called directly.
B.Define the class as abstract using ABC and @abstractmethod.
C.Override __new__ to raise an exception unless called from a classmethod factory.
D.Use a metaclass to prevent instantiation.
AnswerC

As the actual instance-creation hook, __new__ runs before __init__; raising an exception there prevents the instance from ever coming into existence. A classmethod factory can be written to call cls.__new__(cls) while suppressing the guard (e.g., through a private flag), enabling controlled creation. This pattern is the standard way to enforce a factory-only or singleton design, because direct calls to Class() are intercepted at the earliest point.

Why this answer

Overriding `__new__` allows the programmer to control instance creation at the lowest level. By checking the call stack or a flag set by a classmethod factory, `__new__` can raise an exception when instantiation is attempted directly, while still allowing the factory method to create instances. This ensures the class cannot be instantiated directly, only through the designated factory.

Exam trap

Python Institute often tests the distinction between `__new__` and `__init__`, and the trap here is that candidates think raising an exception in `__init__` (Option A) is sufficient, not realizing that `__new__` has already created the object and the exception only prevents full initialization, not allocation.

How to eliminate wrong answers

Option A is wrong because raising an exception in `__init__` still allows the object to be partially created (memory allocated by `__new__`), and the exception can be caught, leaving a half-initialized object or causing confusion; it does not prevent instantiation at the allocation stage. Option B is wrong because defining a class as abstract with ABC and @abstractmethod prevents instantiation only if the class has unimplemented abstract methods; if all abstract methods are implemented, the class can be instantiated directly, which does not enforce the factory-only requirement. Option D is wrong because using a metaclass to prevent instantiation is overly complex and not a standard Python pattern for this specific need; it would require overriding `__call__` in the metaclass, which is less direct and more error-prone than overriding `__new__` in the class itself.

44
MCQhard

A class `ServerConfig` has a class attribute `port = 8080`. After deployment, a developer runs `ServerConfig.port = 9090` in one module, and unexpectedly all existing instances now use port 9090. What concept explains this behavior?

A.Instance attributes always override class attributes.
B.The attribute is immutable.
C.Class attributes are shared among all instances of a class.
D.Python uses copy-on-write for attribute access.
AnswerC

Changing a class attribute via the class affects all instances, as they all reference the same attribute.

Why this answer

Class attributes in Python are shared across all instances of a class. When you modify `ServerConfig.port` on the class itself, every instance that accesses `port` via the class (or via an instance that hasn't overridden it) sees the new value. This is fundamental to Python's attribute lookup mechanism: instance attributes shadow class attributes, but if no instance attribute exists, the class attribute is used.

Exam trap

Python Institute often tests the distinction between modifying a class attribute via the class vs. modifying it via an instance; the trap is that candidates think assigning to `instance.port` changes the class attribute, but it actually creates a new instance attribute that shadows the class attribute.

How to eliminate wrong answers

Option A is wrong because instance attributes only override class attributes when they are explicitly set on the instance; they do not cause class-level changes to propagate. Option B is wrong because the attribute `port` is an integer, which is immutable, but immutability does not affect sharing or rebinding of the attribute on the class. Option D is wrong because Python does not use copy-on-write for attribute access; it uses a dynamic lookup chain (instance → class → parent classes) and assignment always modifies the target directly.

45
MCQhard

A Python developer is implementing a class that should behave like a sequence and support indexing. Which pair of special methods must be defined to achieve this?

A.__getitem__ and __contains__
B.__setitem__ and __delitem__
C.__iter__ and __next__
D.__getitem__ and __len__
AnswerD

According to the Python data model, a sequence is defined primarily by having a `__len__` method and a `__getitem__` method that accepts integer indices (and optionally slices). Together they give the object a finite length, support `obj[i]` indexing, and implicitly enable iteration via the old-style `__getitem__` fallback. On top of these two, the `collections.abc.Sequence` ABC automatically mixes in `__contains__`, `__iter__`, `__reversed__`, `index`, and `count`, showing how much behavior is derived from just these two methods.

Why this answer

To make a class behave like a sequence and support indexing (e.g., obj[0]), Python requires the __getitem__ method to retrieve items by key. Additionally, the __len__ method is needed to define the length of the sequence, which is used by built-in functions like len() and is part of the sequence protocol. Together, these two methods satisfy the minimal requirements for a sequence-like object that supports indexing.

Exam trap

Python Institute often tests the distinction between the sequence protocol (__getitem__ + __len__) and the iterator protocol (__iter__ + __next__), trapping candidates who think iteration alone enables indexing.

How to eliminate wrong answers

Option A is wrong because __contains__ is used for the 'in' operator (membership testing), not for indexing or sequence behavior. Option B is wrong because __setitem__ and __delitem__ are for mutable sequences that support item assignment and deletion, but indexing (read access) only requires __getitem__; __len__ is still needed for sequence protocol. Option C is wrong because __iter__ and __next__ implement the iterator protocol, which allows iteration but does not provide indexing (e.g., obj[0] would fail without __getitem__).

46
Multi-Selecthard

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

Select 3 answers
A.The subclass method must have the same name.
B.The parent method can be called using `super()`.
C.The `@override` decorator is required.
D.The subclass method can have a different return type.
E.The subclass method can have a different number of parameters.
AnswersA, B, D

Method overriding is based on the same method name as in the parent class.

Why this answer

Method overriding in Python requires the subclass method to have the same name as the parent class method. This is the fundamental rule of overriding — without the same name, the method is not overriding but rather defining a new method. The subclass method replaces the inherited method when called on an instance of the subclass.

Exam trap

Python Institute often tests the misconception that Python requires an `@override` decorator (like Java or C#) or that changing the parameter list is allowed in overriding, when in fact Python uses implicit overriding based solely on method name and expects the same signature for correct polymorphic behavior.

47
MCQmedium

A class inherits from two parent classes that both have a method with the same name. When calling the method on the child, only one parent's version is executed. What Python mechanism determines which one?

A.Method overloading by signature.
B.Explicit super() call in the child class.
C.Inheritance depth (closest parent wins).
D.Method Resolution Order (MRO).
AnswerD

MRO determines the order of method lookup in multiple inheritance.

Why this answer

Python uses the C3 linearization algorithm to compute the Method Resolution Order (MRO) for a class. When a method is called on an instance, Python searches the MRO from left to right and executes the first implementation it finds. This ensures a consistent and predictable order of inheritance, even in diamond or multiple-inheritance scenarios.

Exam trap

Python Institute often tests the misconception that Python uses 'closest parent wins' or depth-first search, but the actual mechanism is the C3 linearization MRO, which respects base class order and the diamond inheritance pattern.

How to eliminate wrong answers

Option A is wrong because Python does not support method overloading by signature; the last definition of a method in a class overwrites previous ones, and dispatch is based on the object's type, not argument types. Option B is wrong because an explicit super() call is a way to invoke a parent's method from within the child, but it is not the mechanism that determines which parent's method is executed when calling the method directly on the child. Option C is wrong because inheritance depth does not determine which parent's method is called; Python's MRO follows the C3 linearization order, which respects the order of base classes and the diamond pattern, not simply the closest parent.

48
MCQhard

Refer to the exhibit. What is the output?

A.2 0 0
B.2 2 2
C.2 1 1
D.0 2 2
AnswerB

The class attribute `count` begins at 0, and each call to `__init__` executes `Sample.count += 1`; with two instantiations before any `print`, the shared class variable is exactly 2. Attribute access on an instance first checks that instance's namespace; since neither `a` nor `b` ever assigns `self.count`, both lookups resolve to the same class-level integer. Thus the three printed lines are identical: 2, 2, 2.

Why this answer

The code defines a class `A` with a class variable `x = 2`, and a class `B` that inherits from `A`. The `display` method prints `self.x`, which first looks up the instance attribute `x`; since no instance attribute is set, it falls back to the class variable `x = 2` from class `A`. The loop creates three instances of `B` and calls `display` on each, so each prints `2` on a separate line, resulting in the output 2, 2, 2.

Exam trap

Python Institute often tests the distinction between class variables and instance attributes, and the trap here is that candidates mistakenly think each instance gets its own copy of `x` or that the loop modifies `x` per iteration, when in fact all instances share the same class variable unless explicitly overridden.

How to eliminate wrong answers

Option A is wrong because it suggests the output is 2, 0, 0, which would require the first instance to access the class variable and subsequent instances to have instance attribute `x` set to 0, but no such assignment occurs. Option C is wrong because it shows 2, 1, 1, which would imply that `x` is being modified or that a different value is assigned per instance, but the class variable remains 2 and no instance attribute is created. Option D is wrong because it shows 0, 2, 2, which would require the first instance to have `x = 0` and the rest to have `x = 2`, but no instance-level assignment or override happens in the code.

49
Multi-Selectmedium

Which THREE statements about inheritance in Python are correct?

Select 3 answers
A.A child class inherits all attributes and methods from its parent class.
B.Private attributes (with __) are inherited unchanged.
C.Python supports multiple inheritance.
D.Python supports method overloading based on parameters.
E.The super() function can be used to call a method from a parent class.
AnswersA, C, E

Correct, except for name-mangled ones.

Why this answer

In Python, a child class inherits all non-private attributes and methods from its parent class by default. This includes both data attributes and methods, allowing the child to reuse and extend the parent's behavior without redefinition. Private attributes (with double underscore prefix) are name-mangled to _Classname__attribute, but they are still inherited in the sense that they exist in the child's namespace under the mangled name, though direct access by the original name is restricted.

Exam trap

Python Institute often tests the misconception that private attributes (__name) are completely hidden or not inherited, when in fact they are inherited but name-mangled, and that Python supports method overloading like Java or C++, when it actually relies on default arguments and single method definitions.

50
MCQhard

A developer creates a metaclass 'Meta' that modifies class creation by adding a class attribute 'created_by' set to 'Meta'. Which code snippet correctly defines and uses this metaclass?

A.class Meta(type): def __new__(cls, name, bases, dct): dct['created_by']='Meta'
B.class Meta(type): def __init__(cls, name, bases, dct): dct['created_by']='Meta'
C.def Meta(name, bases, dct): dct['created_by']='Meta'; return type(name, bases, dct)
D.class Meta(type): def __new__(cls, name, bases, dct): dct['created_by']='Meta'; return super().__new__(cls, name, bases, dct)
AnswerD

This correctly overrides `type.__new__`, modifies the mutable namespace dictionary before class construction, and delegates to `super().__new__` to build the class object. Because `super().__new__` copies the entries of `dct` into the new class's `__dict__`, the added `created_by` attribute is present on the finished class. The explicit `return` is essential: without it, no class object would be created at all.

Why this answer

It defines a proper metaclass by subclassing `type` and overriding `__new__`, which is the correct method for modifying the class dictionary before the class is created. The `__new__` method must return the result of `super().__new__(cls, name, bases, dct)` to actually create the class object. Adding `dct['created_by']='Meta'` inside `__new__` ensures the attribute is set during class creation.

Exam trap

Python Institute often tests the distinction between `__new__` and `__init__` in metaclasses, and the trap here is that candidates mistakenly think `__init__` can modify the class dictionary before class creation, or forget that `__new__` must explicitly return the class object.

How to eliminate wrong answers

Option A is wrong because the `__new__` method does not return the newly created class object; without `return super().__new__(...)`, the metaclass returns `None`, causing a `TypeError` when trying to create a class. Option B is wrong because `__init__` is called after the class is already created, so modifying `dct` inside `__init__` does not affect the class's attributes (the dictionary is already used); the correct place to modify the class dictionary is in `__new__`. Option C is wrong because it defines a regular function, not a metaclass; although it can create a class dynamically, it does not define a metaclass that can be used with the `metaclass=Meta` keyword argument in a class statement.

51
MCQhard

An application uses a heavy-weight class DatabaseConnection that establishes a network connection upon instantiation. The class is used in multiple places, and the developer wants to ensure that only one instance of DatabaseConnection exists throughout the application. They implement a Singleton pattern using a class attribute _instance and a class method get_instance(). However, they notice that the network connection is being established multiple times. After debugging, they find that the singleton is not being enforced because the __init__ method is called every time the class is instantiated, even if the same instance is returned. They want to fix this so that the connection is established only once. Which modification should they make?

A.Override __new__ in DatabaseConnection to control instance creation and return the singleton from there, bypassing __init__ on subsequent calls.
B.Use the @staticmethod decorator on get_instance.
C.Use a global variable instead of a class attribute to store the singleton.
D.Move the connection initialization code out of __init__ and into a separate method that is called only once.
AnswerA

Overriding __new__ allows you to return the existing instance before __init__ is called, preventing repeated initialization.

Why this answer

Overriding __new__ allows the developer to control instance creation at the lowest level. By checking if the singleton already exists in __new__, they can return the existing instance without calling __init__ again, thus preventing the network connection from being established multiple times. This is the standard Pythonic way to implement a singleton that avoids reinitialization.

Exam trap

Python Institute often tests the distinction between instance creation (__new__) and instance initialization (__init__), trapping candidates who think that simply returning the same instance from a class method is enough to prevent reinitialization.

How to eliminate wrong answers

Option B is wrong because using @staticmethod on get_instance does not prevent __init__ from being called each time the class is instantiated; it only changes how the method is invoked. Option C is wrong because using a global variable instead of a class attribute does not solve the core issue: __init__ will still be called on every instantiation attempt, re-establishing the connection. Option D is wrong because moving the initialization code to a separate method does not prevent that method from being called multiple times if the singleton pattern is not properly enforced at the instance creation level.

Ready to test yourself?

Try a timed practice session using only Oop questions.