Courseiva

CCNA Advanced Object Oriented Programming Questions

68 questions · Advanced Object Oriented Programming · All types, answers revealed

1
MCQhard

A library defines a base class `Plugin` with `__init_subclass__(cls, **kwargs)` that automatically registers every subclass in a global registry using `cls.__name__`. A developer writes `class MyPlugin(Plugin, priority=5): pass` but the registration fails with a TypeError about unexpected keyword arguments. What is the most likely cause?

A.The base `Plugin.__init_subclass__` signature does not accept or forward `**kwargs`, so `priority=5` reaches `object.__init_subclass__`, which rejects it.
B.`__init_subclass__` must be declared as a classmethod explicitly; otherwise keyword arguments are not forwarded.
C.The registry is full because `__init_subclass__` runs before the class body executes, so `cls.__name__` is not yet available.
D.Keyword arguments passed in a class definition header are only supported on metaclasses, not on `__init_subclass__`.
AnswerA

If `Plugin.__init_subclass__` is defined as `def __init_subclass__(cls):` without `**kwargs`, then the `priority=5` keyword is not consumed. Python then calls `super().__init_subclass__(**kwargs)` implicitly, ultimately reaching `object.__init_subclass__`, which raises TypeError for unexpected arguments. Adding `**kwargs` to the signature and forwarding it resolves the error.

Why this answer

Keyword arguments in a class header are forwarded to the parent's `__init_subclass__`. If that method does not declare `**kwargs` and forward them, the extra keyword reaches `object.__init_subclass__`, which rejects it with TypeError. Adding `**kwargs` to the signature and passing them to `super().__init_subclass__(**kwargs)` fixes the registration.

Exam trap

The trap here is assuming that `__init_subclass__` automatically absorbs arbitrary keyword arguments without the developer declaring and forwarding them.

2
MCQeasy

Which of the following describes the purpose of the __slots__ attribute in a class?

A.To allow private attribute access.
B.To define the method resolution order.
C.To improve performance and reduce memory usage.
D.To prevent inheritance.
AnswerC

__slots__ declares a fixed set of instance attributes, replacing the per-instance __dict__ with a compact slot descriptor array. This removes dictionary overhead, reducing memory usage per object and speeding attribute access, which is the stated purpose of defining __slots__.

Why this answer

__slots__ restricts the creation of new instance attributes and reduces memory footprint by preventing the creation of a per-instance __dict__.

3
MCQmedium

A developer defines a class `Temperature` that stores a Celsius value in `self._celsius` and exposes a `celsius` property with a setter that validates the value is not below absolute zero. After instantiating `t = Temperature(20)`, the developer runs `t.celsius = -300`. What happens?

A.The setter runs, detects the invalid value, and raises a ValueError; `t._celsius` remains 20.
B.A TypeError is raised because `t.celsius` is read-only once the property is defined without a deleter.
C.The assignment silently succeeds and `t.celsius` returns -300 because properties only intercept reads, not writes.
D.Python bypasses the setter because `_celsius` is a private attribute and assigns -300 directly to `t._celsius`.
AnswerA

Assigning to `t.celsius` invokes the property's setter function, which contains the validation logic. Since -300 is below absolute zero, the setter raises ValueError before modifying `self._celsius`, so the attribute keeps its previous value of 20. This is the intended use of properties to enforce invariants on attribute assignment.

Why this answer

The property setter is invoked on assignment to `t.celsius`, and its validation logic rejects -300. Because the exception is raised before `self._celsius` is updated, the original value remains intact. This demonstrates how properties enforce encapsulation and invariants without changing the public attribute name.

Exam trap

The trap here is assuming that a leading underscore on the backing attribute causes Python to bypass the property setter during assignment.

4
MCQmedium

You are designing a plugin system where each plugin class must automatically register itself with a central registry when defined, without requiring an explicit decorator or registration call. You decide to use the __init_subclass__ hook in the base Plugin class. Which statement correctly describes how to implement this so that every subclass is registered with its own class object?

A.Define __init_subclass__ as an instance method in the base class that receives self and uses type(self) to identify the subclass.
B.Override __new__ in the base class to intercept subclass creation and register the subclass there.
C.Use a metaclass with a __call__ method that registers the class when the first instance is created.
D.Define __init_subclass__ as a classmethod in the base class that receives the new subclass as its first argument and adds it to a registry list.
AnswerD

When a subclass is created, Python automatically calls the parent's __init_subclass__ with the new class as the first argument. Defining it as a classmethod in the base class lets you capture that class and append it to a registry, achieving automatic registration without decorators.

Why this answer

The __init_subclass__ hook is invoked automatically on the parent class whenever a subclass is defined, receiving the new subclass as an argument. Implementing it as a classmethod in the base Plugin class allows the base to append each subclass to a shared registry at definition time, satisfying the automatic registration requirement without decorators or manual calls.

Exam trap

The trap here is assuming __init_subclass__ must be explicitly decorated or that it runs on instances rather than on the parent class during subclass creation.

5
MCQeasy

Which magic method is responsible for providing the informal string representation of an object, typically used for user-facing output?

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

`__str__` returns the informal, human-readable string representation, which `print()` and `str()` invoke for user-facing output. The alternative `__repr__` targets developers and aims for an unambiguous, ideally evaluable representation. The stem's constraint — informal, user-facing display — maps directly onto `__str__`, making it the responsible magic method.

Why this answer

The __str__ method is used for informal, readable string representations, whereas __repr__ is for formal, unambiguous representations.

6
MCQmedium

Which magic method allows an object to behave like a function?

A.__apply__
B.__invoke__
C.__func__
D.__call__
AnswerD

Defining `__call__` on a class makes its instances callable, so `obj()` invokes that method directly — the exact behaviour the stem requires. Python resolves the call on the instance rather than the class, letting the object behave like a function while retaining its own state.

Why this answer

The __call__ method enables an object to be invoked with parentheses syntax.

7
MCQmedium

A developer writes a class `Money` that implements `__eq__` to compare amounts and currencies, but does not implement `__hash__`. Instances of `Money` are then used as keys in a `set`. What is the result?

A.Instances are hashed using the hash of their `__dict__`, so two equal `Money` objects collapse into one set element.
B.The set stores instances by identity, so duplicates are allowed and no error occurs.
C.Instances can be added to a set, and equality is used for deduplication because `__hash__` defaults to `id()`-based hashing.
D.A TypeError is raised when adding an instance to the set because the class is unhashable.
AnswerD

Defining `__eq__` without `__hash__` causes Python to set `__hash__ = None` on the class, marking instances as unhashable. Any attempt to use them in a set or as a dict key raises TypeError: unhashable type. To fix this, the developer must explicitly define `__hash__`, typically based on the same attributes used in `__eq__`.

Why this answer

Python disables the inherited `__hash__` when a class overrides `__eq__`, because equal objects must have equal hashes for hash-based collections to work correctly. The developer must define `__hash__` explicitly, hashing the same attributes used in `__eq__`, so that equal `Money` instances produce identical hashes and can be stored in sets or dicts.

Exam trap

The trap here is assuming that overriding `__eq__` leaves the default identity-based `__hash__` in place so instances remain usable in sets.

8
MCQmedium

In the Factory pattern, what should you do if the requested type is unknown?

A.Log the error and return the base class.
B.Raise a ValueError.
C.Return a default instance.
D.Return None.
AnswerB

An unrecognised type string means the factory cannot construct a valid product, so silently returning None would defer failure. Raising ValueError signals the invalid argument immediately at the call site, letting callers handle the unsupported request explicitly.

Why this answer

Raising a ValueError or a custom 'UnknownTypeException' is the standard way to handle invalid inputs in a factory.

9
MCQhard

You are implementing a `Vector` class whose instances should support `v1 + v2` and also `v1 + 5`, where the second form broadcasts the scalar across every component. A colleague suggests relying on `__radd__` to handle the scalar case. Why is that insufficient?

A.`__radd__` receives the operands in the same order as `__add__`, so it cannot distinguish which operand is the vector and which is the scalar.
B.`__radd__` is reserved for augmented assignment, so `v1 += 5` would work while `v1 + 5` would raise a `TypeError` regardless of what `__add__` returns.
C.`__radd__` is only invoked for subclasses of the left operand's type, so a plain `int` on the left can never trigger it under any circumstances.
D.`__radd__` is only consulted when the left operand's `__add__` returns `NotImplemented` or does not exist, so `Vector.__add__` must first detect and return `NotImplemented` for unsupported operand types.
AnswerD

Python evaluates `v1 + 5` by calling `Vector.__add__(v1, 5)` first. Only if that returns the `NotImplemented` singleton does the interpreter try `int.__radd__(5, v1)`. Therefore `__radd__` cannot intercept the scalar case unless `__add__` explicitly yields `NotImplemented`, making a cooperative `__add__` essential to the design.

Why this answer

Binary operator dispatch tries the left operand's forward method first. For `v1 + 5`, that is `Vector.__add__`, which must return the `NotImplemented` singleton to signal that it cannot handle an `int`. Only then does Python attempt the reflected operation.

Relying on `__radd__` alone leaves `__add__` free to raise or misbehave, so the two methods must be designed as a pair.

Exam trap

The trap here is believing `__radd__` is tried first or independently, when it only runs after the left operand's forward method returns `NotImplemented`.

10
MCQhard

A teammate writes a `Money` value object that defines `__eq__` to compare amounts and currencies, and `__hash__` to hash the same two fields. During a code review you notice that a subclass `Discount` adds a `percentage` field, overrides only `__eq__` to include that field, and leaves `__hash__` inherited. Objects of `Discount` are then used as dictionary keys. What is the actual runtime consequence?

A.Python raises a `SyntaxError` at class-definition time because overriding `__eq__` without also overriding `__hash__` is statically rejected by the compiler.
B.The dictionary works correctly because `dict` uses `id()`-based hashing for user-defined classes whenever `__hash__` is ambiguous, sidestepping the overridden `__eq__`.
C.The inherited `Money.__hash__` is used, and because it hashes only amount and currency, two `Discount` objects with different percentages but equal amount and currency collide into one dictionary entry.
D.Because `Discount` defines `__eq__`, Python sets `Discount.__hash__` to `None` in that class, so instances become unhashable and dictionary insertion raises `TypeError`.
AnswerD

When a class defines `__eq__` without defining `__hash__`, Python implicitly sets that class's `__hash__` attribute to `None`, regardless of any `__hash__` inherited from a base class. Instances of `Discount` therefore cannot be hashed, and using one as a dictionary key raises `TypeError: unhashable type`. The inherited `Money.__hash__` does not save the subclass from this rule.

Why this answer

Python enforces an invariant between equality and hashing: if a class overrides `__eq__`, the default identity hash is no longer consistent with the new equality, so the interpreter sets `__hash__` to `None` in that class. Defining `__eq__` in `Discount` without a matching `__hash__` makes its instances unhashable, and inheritance from `Money` does not restore hashability. Dictionary keys must therefore be plain `Money` objects or the subclass must define its own `__hash__`.

Exam trap

The trap here is assuming that a `__hash__` inherited from a base class remains in effect after a subclass redefines `__eq__`.

11
MCQmedium

What is the difference between a class-level variable and an instance-level variable?

A.Instance-level variables cannot be modified.
B.Class-level variables are always constant.
C.Class-level variables are hidden from subclasses.
D.Class-level variables are shared by all instances.
AnswerD

Class-level variables live on the class object itself and are therefore shared across every instance, whereas instance-level variables are stored per object in its __dict__. Assignment through an instance shadows rather than mutates the class attribute.

Why this answer

A class-level variable is shared across all instances of the class, whereas an instance-level variable is specific to each object.

12
MCQmedium

You are designing a class `Matrix` that supports element-wise addition and scalar multiplication. You want `m1 + m2` to return a new `Matrix` without modifying `m1` or `m2`. You have implemented `__add__` correctly. A colleague writes `m1 += m2` and expects `m1` to be rebound to a new `Matrix` object. However, after the statement, `m1` still refers to the same object and its internal data has been modified in place. Which change to your class is the most likely cause of this behavior?

A.You defined `__iadd__` to call `__add__` and then return `self` after updating the internal list in place.
B.You defined `__iadd__` to return a new `Matrix` instance without modifying `self`.
C.You defined `__radd__` to return a new `Matrix` instance instead of `NotImplemented`.
D.You defined `__add__` to modify `self` in place and return `self`.
AnswerA

Defining `__iadd__` overrides the fallback to `__add__` for the `+=` operator. If `__iadd__` mutates the instance and returns `self`, the name `m1` is rebound to the same object, and its internal state changes. This exactly matches the observed behavior.

Why this answer

The augmented assignment `+=` first looks for `__iadd__` on the left operand. When `__iadd__` is defined and mutates the object before returning `self`, the name is rebound to the same object and the original data changes. Without `__iadd__`, Python falls back to `__add__`, which produces a new object and rebinds the name.

Exam trap

The trap here is assuming that `+=` always behaves like `+` and rebinds to a new object, overlooking that defining `__iadd__` can make augmented assignment mutate in place.

13
MCQmedium

When overriding the __getattr__ method, what must you be careful to avoid?

A.Infinite recursion by accessing non-existent attributes.
B.Raising AttributeError.
C.Accessing class attributes.
D.Calling super().__getattr__.
AnswerA

Accessing a missing attribute inside `__getattr__` re-invokes `__getattr__`, because the attribute lookup fails again, producing infinite recursion until Python raises `RecursionError`. Guard by raising `AttributeError` for unknown names or delegating to `super().__getattr__()`, satisfying the stem's requirement to avoid this pitfall.

Why this answer

Infinite recursion occurs if you try to access an attribute inside __getattr__ that does not exist, triggering __getattr__ again.

14
MCQmedium

A developer creates a class named `Registry` in a module. The class inherits from `ABCMeta` and defines a `__new__` method that performs validation on the class namespace before the class is created. Inside `__new__`, the developer needs to inspect the attributes defined in the class body of the class being created. Which parameter of `__new__` provides direct access to those attributes?

A.The `bases` parameter.
B.The `**kwargs` parameter.
C.The `namespace` parameter.
D.The `mcls` parameter.
AnswerC

In `ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)`, the fourth positional parameter is the namespace dictionary containing all attributes defined in the class body. Accessing it before calling `super().__new__` lets the developer validate or modify attributes during class creation, exactly matching what the scenario requires.

Why this answer

The namespace dictionary is the fourth positional argument supplied to `ABCMeta.__new__`. It holds every name bound in the class body before the class object exists, which is exactly the data needed for validation during class creation. The other parameters describe the metaclass, the parent tuple, or extra keywords, none of which carry class-body attributes.

Exam trap

The trap here is confusing the metaclass `__new__` signature parameters, assuming `bases` or `mcls` carries the class body attributes.

15
MCQmedium

When using a metaclass, how can you access the class attributes during the class definition?

A.By calling super().
B.By accessing the class __dict__.
C.By querying the base class.
D.By accessing the namespace dictionary passed to __new__.
AnswerD

During class creation the metaclass's __new__ receives the namespace dictionary containing all attributes defined in the class body, so reading it there exposes them before the class object exists. __init__ receives the already-built class instead, making __new__ the correct hook.

Why this answer

The class dictionary passed to the metaclass's __new__ method contains all the attributes defined within the class body.

16
MCQeasy

You are creating a class where you want to prevent instances from having a __dict__ to save memory. You add __slots__ = ('x', 'y') to the class. Which statement is true about this class?

A.The class can still accept arbitrary attributes, but they will be stored in a separate dictionary.
B.Instances will still have a __dict__, but x and y will be stored in slots for faster access.
C.Subclasses of this class will also be prevented from having a __dict__ automatically.
D.Instances of the class will not have a __dict__ and can only have attributes x and y.
AnswerD

Defining __slots__ with 'x' and 'y' tells Python to allocate space for exactly those attributes in a compact structure, and the instance __dict__ is not created. Attempting to assign any other attribute raises AttributeError. This reduces memory overhead per instance.

Why this answer

The __slots__ declaration restricts instances to the named attributes and eliminates the per-instance __dict__, saving memory. Only attributes listed in __slots__ can be assigned; others raise AttributeError. Subclasses do not automatically inherit this restriction unless they also define __slots__.

Exam trap

The trap here is assuming subclasses automatically inherit the no-__dict__ behavior, when they must declare __slots__ themselves to maintain it.

17
Multi-Selectmedium

Which TWO of the following are true regarding multiple inheritance in Python?

Select 2 answers
A.It is not supported.
B.super() is used for cooperative method calls.
C.The MRO is determined at runtime.
D.It uses the C3 linearization algorithm.
E.Multiple inheritance is always preferred over composition.
AnswersB, D

super() follows the MRO, delegating to the next class in the linearised chain rather than a fixed parent, which enables cooperative multiple inheritance. Each class calls super() so sibling branches all execute exactly once, satisfying the stem's requirement for a true statement about multiple inheritance.

Why this answer

Option B is correct because in Python's multiple inheritance, super() does not simply call the parent class but follows the Method Resolution Order to invoke the next class in the chain, enabling cooperative multiple inheritance where each class's method calls super() to pass control along the MRO. Option D is correct because Python resolves the method resolution order for multiple inheritance using the C3 linearization algorithm, which produces a consistent, monotonic ordering that respects local precedence and subclass relationships. Option A is incorrect because Python explicitly supports multiple inheritance, allowing a class to inherit from several base classes.

Option C is incorrect because the MRO is computed at class creation time (when the class is defined), not dynamically at runtime for each method call. Option E is incorrect because composition is often preferred over multiple inheritance for maintainability and avoiding the diamond problem; multiple inheritance is not always preferred.

18
MCQhard

You are implementing a class `Vector` that supports addition and scalar multiplication. You want `v1 + v2` to return a new `Vector`, and `v1 * 3` to return a new scaled `Vector`. You also want `3 * v1` to work identically. Which pair of methods must you implement to support both `v1 * 3` and `3 * v1` correctly, assuming `v1` is a `Vector` and `3` is an `int`?

A.`__rmul__` and `__radd__`
B.`__mul__` only, because Python automatically swaps operands.
C.`__mul__` and `__rmul__`
D.`__mul__` and `__imul__`
AnswerC

`__mul__` handles `v1 * 3` because the left operand is a `Vector`. For `3 * v1`, Python first tries `int.__mul__(3, v1)`, which returns `NotImplemented` because `int` does not know how to multiply by a custom type. Python then calls `Vector.__rmul__(v1, 3)`. Implementing both ensures both orders work and return a new `Vector`.

Why this answer

To support both `v1 * 3` and `3 * v1`, the class must define `__mul__` for the normal case and `__rmul__` for the reflected case. When the left operand's `__mul__` returns `NotImplemented`, Python tries the right operand's `__rmul__`. Implementing both ensures consistent behavior regardless of operand order, which is important for intuitive mathematical types.

Exam trap

The trap here is believing that defining `__mul__` alone is enough because Python will reorder operands automatically.

19
MCQmedium

You are designing a class hierarchy for a document processing system. The base class `Document` defines a method `render()` that returns a string. You want subclasses to be forced to provide their own implementation, and you want to prevent `Document` from being instantiated directly. Which code correctly enforces this requirement?

A.class Document: def render(self): raise NotImplementedError("Subclass must implement render") def __init__(self): if self.__class__ is Document: raise TypeError("Cannot instantiate Document directly")
B.from abc import ABC, abstractmethod class Document(ABC): @abstractmethod def render(self): pass
C.class Document: def render(self): pass def __new__(cls, *args, **kwargs): if cls is Document: raise TypeError("Cannot instantiate Document directly") return super().__new__(cls)
D.class Document: def render(self): raise Exception("Not implemented") class DocumentMeta(type): def __call__(cls, *args, **kwargs): if cls is Document: raise TypeError("Cannot instantiate Document directly") return super().__call__(*args, **kwargs)
AnswerB

Using ABC and @abstractmethod makes Document uninstantiable and forces subclasses to implement render() before they can be instantiated. This is the standard, explicit way to define an abstract base class in Python. Any subclass that does not override render() remains abstract and raises TypeError on instantiation, precisely matching the requirement to force implementation and prevent direct instantiation.

Why this answer

The abstract base class pattern with ABC and @abstractmethod is the idiomatic way to prevent direct instantiation of a base class and force subclasses to implement specific methods. It ensures that any subclass lacking an override remains abstract and cannot be instantiated, raising a TypeError. The other approaches either fail to enforce implementation or rely on fragile runtime checks.

Exam trap

The trap here is assuming that raising NotImplementedError in a method is equivalent to an abstract method, when it only fails at call time rather than preventing instantiation.

20
MCQhard

What is the primary risk of using 'getattr' without a default value?

A.It returns None.
B.It enters an infinite loop.
C.It raises an AttributeError.
D.It creates the attribute.
AnswerC

Without a default, getattr raises AttributeError when the named attribute is absent from the object. That unhandled exception is the primary risk, since callers must anticipate and catch it rather than receiving a fallback value.

Why this answer

If the attribute is not found, it raises an AttributeError, potentially crashing the application if not handled.

21
MCQmedium

When using the __call__ method to implement a decorator, what does the decorator receive as an argument?

A.The arguments to the function.
B.The instance of the class.
C.The class object.
D.The function to be decorated.
AnswerD

A class defining `__call__` receives the function being decorated as its initialiser argument, storing it for later invocation. This satisfies the stem's requirement: the decorator object wraps the target function, then `__call__` intercepts subsequent calls, forwarding arguments and returning the wrapped result.

Why this answer

The decorator receives the function object that is being decorated as the argument to the constructor (if the decorator is a class instance) or directly if it is a function.

22
MCQhard

Why does the __init__ method not return anything?

A.Because it is designed to initialize an existing instance.
B.Because of internal Python constraints.
C.To allow for multiple initialization.
D.Because it's a generator.
AnswerA

__init__ initialises an already-created instance in place, so it must return None; returning any other value raises TypeError. Object construction itself is handled by __new__, which allocates and returns the instance, so __init__ has nothing meaningful to return.

Why this answer

The __init__ method is for initialization only; the object creation is handled by __new__.

23
MCQhard

A developer implements a class `Vector` with `__add__` returning a new `Vector`, and also defines `__iadd__` that mutates `self` in place and returns `self`. A colleague writes `a = Vector(1,2); b = Vector(3,4); c = a; a += b`. What is the state of `c`?

A.`c` raises an AttributeError on access because `__iadd__` invalidates all prior references to the mutated object.
B.`c` now reflects the updated values because `__iadd__` mutated the shared object that `c` also references.
C.`c` is unchanged because `+=` always creates a new object, leaving the original binding intact.
D.`c` is unchanged because the augmented assignment rebinds `a` to a new object produced by `__add__`, ignoring `__iadd__`.
AnswerB

`a += b` invokes `Vector.__iadd__`, which mutates `self` and returns it. Because `c = a` binds `c` to the same object, the in-place mutation is visible through `c`. This aliasing effect is the defining behavior of in-place operators and can surprise developers who expect `+=` to rebind a new object.

Why this answer

Augmented assignment prefers the in-place method `__iadd__` over `__add__`. When `__iadd__` mutates the object and returns it, any other references to that same object see the updated state. This aliasing behavior is a common source of subtle bugs when mutable objects are shared between variables.

Exam trap

The trap here is believing that `+=` always produces a new object, when in fact `__iadd__` can mutate the existing instance in place.

24
MCQhard

What is the purpose of the 'weakref' module in relation to the Observer pattern?

A.To allow private access to observers.
B.To ensure thread safety.
C.To prevent memory leaks.
D.To serialize observers.
AnswerC

Observers registered with a subject via strong references keep each other alive, so unregistered observers are never collected. weakref holds observers without incrementing their reference count, letting garbage collection reclaim them and preventing the memory leaks that accumulate over long-lived subjects.

Why this answer

It allows the subject to maintain references to observers without preventing their collection by the garbage collector.

25
MCQhard

How do you define a property in a class to make it read-only?

A.Use the 'readonly' decorator.
B.Use the 'final' modifier.
C.Set the attribute to private.
D.Define a property with only @property, no @setter.
AnswerD

Using only the `@property` decorator creates a read-only attribute: the getter runs on access, but any assignment raises `AttributeError` because no setter exists. This directly satisfies the stem's read-only constraint, unlike a plain attribute, which remains freely writable.

Why this answer

By defining the property with only a getter (using @property) and omitting the setter method.

26
MCQeasy

Which magic method is used to control how an instance is displayed when using the print() function?

A.__str__
B.__display__
C.__repr__
D.__print__
AnswerA

`__str__` supplies the informal, human-readable string that `print()` calls when converting an instance to text. Python's `print()` invokes `str()` on its arguments, which delegates to `__str__`; `__repr__` only serves as a fallback when `__str__` is undefined. This directly satisfies the stem's requirement for controlling printed output.

Why this answer

print() implicitly calls str(obj), which in turn calls __str__.

27
MCQmedium

A developer is implementing the Observer pattern and wants to store a list of observers without preventing them from being garbage collected when no other references exist. They decide to use `weakref.WeakSet` to hold the observers. Which statement accurately describes the behavior they should expect?

A.Observers continue to receive notifications until the `WeakSet` itself is garbage collected.
B.Observers are automatically removed from the set when their last strong reference is gone.
C.Calling `len()` on a `WeakSet` raises `ReferenceError` once any member is collected.
D.Observers must be hashable and are kept alive by the `WeakSet` until explicitly removed.
AnswerB

`WeakSet` stores weak references, so when an observer object has no other strong references, it is collected and the set entry disappears silently. This is precisely the behavior wanted for the Observer pattern to avoid keeping observers alive indefinitely. No manual unregistration is required for objects that go out of scope.

Why this answer

A `WeakSet` holds weak references and drops entries when the referenced objects are garbage collected. That makes it ideal for observer registries where you do not want the subject to keep observers alive. The other statements misstate weak reference semantics or confuse `WeakSet` behavior with `weakref.ref` dereferencing errors.

Exam trap

The trap here is conflating `weakref.ref` dereference errors with `WeakSet` behavior, or assuming the set keeps members alive.

28
MCQmedium

In the context of the Observer pattern, what is the benefit of an event-driven system over a direct-method-call approach?

A.Reduced memory usage.
B.Increased performance.
C.Decoupling between the subject and observers.
D.Automatic thread synchronization.
AnswerC

Observers register callbacks with the subject rather than being invoked by name, so the subject holds no compile-time reference to concrete observer classes. This satisfies the stem's decoupling constraint, letting observers be added or removed at runtime.

Why this answer

Decoupling is the primary benefit; the subject does not need to know the implementation details of the observers.

29
MCQhard

What is the purpose of the __set_name__ method in descriptors?

A.To set the name of the class.
B.To delete the attribute.
C.To rename the attribute.
D.To allow the descriptor to know its name in the owner class.
AnswerD

__set_name__ is called automatically at class creation with the owner class and the attribute name, letting the descriptor capture its own identifier. This removes the need to pass the name manually, enabling descriptors to store or validate per-attribute data.

Why this answer

__set_name__ is called at class creation time to inform the descriptor of its name in the owner class, allowing it to store values in a private attribute without needing to know the name beforehand.

30
MCQmedium

Why should you use super() instead of calling the parent class method directly by name?

A.It is required for private methods.
B.It correctly resolves the next method in the MRO.
C.It is faster.
D.It makes the code more verbose.
AnswerB

super() delegates to the next class in the method resolution order rather than a hard-coded parent, so cooperative multiple inheritance chains correctly. This satisfies the stem's requirement by avoiding the brittle, MRO-breaking direct parent call.

Why this answer

super() is preferred over calling the parent class method directly because it correctly resolves the next method in the Method Resolution Order (MRO). This ensures that in multiple inheritance scenarios, all appropriate parent classes are called in the correct order, avoiding duplication or skipping.

Exam trap

PCPP1 often tests the misconception that super() is just a shortcut for calling the parent class; candidates may not realize it follows the MRO, which is essential for multiple inheritance.

How to eliminate wrong answers

Option A is wrong because super() is not required for private methods; private methods (name-mangled) are not inherited in the same way and super() does not affect them. Option C is wrong because super() is not primarily about speed; it may be slightly slower due to MRO lookup, but the main benefit is correctness. Option D is wrong because super() typically makes code less verbose and more maintainable, not more verbose.

31
MCQmedium

What is the purpose of the 'type' function when called with three arguments?

A.To register a class.
B.To create a new class dynamically.
C.To inherit from multiple classes.
D.To check object type.
AnswerB

Calling `type(name, bases, dict)` invokes the three-argument metaclass constructor, which builds a fresh class object at runtime from the supplied name string, base-class tuple and namespace dictionary. This satisfies the stem's requirement precisely: the three-argument form exists to create classes dynamically, unlike the single-argument form that merely returns an object's type.

Why this answer

It is used for dynamic class creation: type(name, bases, dict).

32
MCQmedium

What does the @abstractmethod decorator do when applied to a method inside an ABC?

A.It requires the subclass to implement the method.
B.It prevents the method from being called.
C.It automatically generates a default implementation.
D.It makes the method faster.
AnswerA

The @abstractmethod decorator marks a method as abstract within an ABC, so any concrete subclass that does not override it cannot be instantiated; Python raises TypeError at instantiation. This enforces the subclass implementation the question describes.

Why this answer

It marks the method as requiring an override in any non-abstract concrete subclass.

33
MCQmedium

When using abstract base classes (ABCs) from the abc module, what happens if a class inherits from an ABC but fails to implement one of the abstract methods?

A.The class will be created, but calling the missing method will raise an AttributeError.
B.The class will be marked as abstract and will raise a TypeError upon instantiation.
C.The missing method will default to a 'pass' statement.
D.The parent ABC will automatically provide a NotImplementedError.
AnswerB

Inheriting an ABC without overriding every abstract method leaves the subclass abstract, so Python refuses instantiation and raises TypeError. This satisfies the stem's constraint: the missing implementation is detected at instantiation time, not at class definition, because ABCMeta records remaining abstract methods in `__abstractmethods__`.

Why this answer

Python will raise a TypeError when you attempt to instantiate the class, preventing the creation of an incomplete object.

34
MCQmedium

What is the consequence of not calling super().__init__() in a class with multiple inheritance?

A.The class will not be created.
B.All attributes will be lost.
C.The initialization chain for parent classes is interrupted.
D.Python will raise a RuntimeError.
AnswerC

Skipping super().__init__() breaks Python's cooperative multiple-inheritance chain: the MRO traversal halts at that class, so subsequent parent initialisers never execute. This satisfies the stem's constraint that later classes in the MRO remain uninitialised, leaving their instance attributes unset.

Why this answer

The MRO chain will break, and sibling classes may not be initialized properly, potentially causing missing attributes or logic errors.

35
MCQeasy

Which magic method is triggered when an object is deleted using the 'del' keyword?

A.__clear__
B.__del__
C.__remove__
D.__destroy__
AnswerB

`__del__` is the destructor magic method invoked when an object's reference count reaches zero or the interpreter shuts down, satisfying the stem's requirement for deletion via `del`. Note that `del` removes a reference rather than guaranteeing immediate finalisation, so `__del__` may run later under cyclic references.

Why this answer

The __del__ method is called when the object's reference count reaches zero.

36
MCQhard

What is the result of applying the @staticmethod decorator to a method inside a class that also defines a metaclass?

A.The metaclass must manually convert it back to a static method.
B.It raises a TypeError at class definition time.
C.The method is correctly bound as a static function regardless of the metaclass.
D.The metaclass will treat the method as an instance method.
AnswerC

The `@staticmethod` decorator wraps the function in a descriptor that returns the plain function without binding `self` or `cls`, so metaclass interference is irrelevant. Metaclasses control class creation, not instance attribute lookup, so the static method resolves correctly on both the class and its instances.

Why this answer

The @staticmethod decorator creates a static method object which is stored in the class dictionary; it is independent of the metaclass's __init__ logic for instance methods.

37
MCQmedium

You are writing a class `Temperature` that should support addition and comparison. You want `t1 + t2` to return a new `Temperature` object, and `t1 == t2` to compare their values. Which magic methods must you implement to achieve this?

A.__radd__ and __eq__
B.__add__ and __cmp__
C.__iadd__ and __eq__
D.__add__ and __eq__
AnswerD

__add__ is called for the + operator and should return a new Temperature instance. __eq__ is called for == and should compare the underlying values. Implementing these two methods provides the required behavior. Without __add__, + would raise TypeError; without __eq__, == would fall back to identity comparison, which is not desired.

Why this answer

To support the + operator, a class must implement __add__. For equality comparison with ==, it must implement __eq__. These two methods directly correspond to the requested operations.

Other methods like __iadd__ or __radd__ serve different purposes and are not required for the basic behavior described.

Exam trap

The trap here is confusing __add__ with __iadd__ or __radd__, which handle in-place addition and reflected addition respectively, not the standard + operator.

38
MCQhard

What is the effect of setting __slots__ in a parent class on a child class that does not define its own __slots__?

A.The child class will have a __dict__ attribute, overriding the parent's optimization.
B.The child class will raise an error at instantiation.
C.The child class will inherit the memory savings automatically.
D.The child class will be unable to add new attributes.
AnswerA

Omitting __slots__ in the child reinstates a per-instance __dict__, so the parent's memory optimisation no longer applies to child instances. The child still inherits the parent's slot descriptors, but new attributes are stored in the dictionary, satisfying the scenario where the subclass defines no __slots__.

Why this answer

The child class will receive an instance __dict__ unless it also defines __slots__, effectively defeating the memory optimization of the parent.

39
MCQeasy

A `Temperature` class stores a numeric `celsius` attribute. Callers keep assigning `temp.celsius = -500`, which is physically impossible. You want any assignment below absolute zero to raise `ValueError` while keeping normal attribute access unchanged. Which construct should you implement?

A.A `property` named `celsius` whose setter validates the incoming value before storing it in a private attribute.
B.Implement `__getattribute__` to compute `celsius` on demand and reject invalid reads with `ValueError`.
C.Define `__slots__ = ('celsius',)` so that Python enforces type and range constraints on the slot automatically.
D.Override `__setattr__` on the class to accept a value only when the attribute name is `celsius` and the value is valid.
AnswerA

A `property` intercepts attribute access through descriptor protocol methods, so the setter can validate before writing to an internal field such as `_celsius`. Reads remain transparent because the getter simply returns the stored value. This keeps the public name `celsius` while enforcing the constraint at assignment time, which is exactly the requested behavior.

Why this answer

A `property` is the idiomatic way to validate a single attribute: its setter runs on every assignment and can raise `ValueError` before the value is stored, while the getter preserves ordinary read syntax. Broader hooks like `__setattr__` or `__getattribute__` affect all attributes and are harder to reason about. Slots control structure, not values, so they cannot enforce the physical limit.

Exam trap

The trap here is reaching for `__setattr__` when the validation applies to only one attribute that a `property` can handle cleanly.

40
MCQhard

You are implementing a class `Matrix` that supports the `+` operator for matrix addition. You want `m1 + m2` to return a new `Matrix`, and you also want `sum([m1, m2, m3])` to work correctly. You define `__add__` to return a new `Matrix`. However, `sum([m1, m2, m3])` raises a `TypeError`. What is the most likely reason?

A.`sum()` calls `__add__` on the first element, but `Matrix.__add__` expects another `Matrix`, and `0` is not a `Matrix`.
B.`sum()` starts with `0` as the initial value, and `0 + m1` fails because `int.__add__` returns `NotImplemented` and `Matrix` lacks `__radd__`.
C.`sum()` requires the `__iadd__` method for in-place addition.
D.`sum()` requires all elements to be of the same type, and `0` is an `int`.
AnswerB

`sum()` begins with `0` and adds each element. The first operation is `0 + m1`, which calls `int.__add__(0, m1)` and returns `NotImplemented` because `int` cannot add a `Matrix`. Python then tries `Matrix.__radd__(m1, 0)`. If `__radd__` is not defined, a `TypeError` is raised. Implementing `__radd__` to handle the initial `0` (or any non-Matrix) fixes the issue.

Why this answer

`sum()` starts with `0` and adds each item. The first addition is `0 + m1`, which fails because `int.__add__` returns `NotImplemented` and `Matrix` does not define `__radd__`. To support `sum()`, you must implement `__radd__` to handle the initial `0` and any other non-Matrix left operand.

This makes the class compatible with the built-in `sum()` function.

Exam trap

The trap here is forgetting that `sum()` begins with `0`, so the first operation involves an `int` on the left, requiring a reflected addition method.

41
Multi-Selecthard

Which THREE of the following are potential pitfalls of the Singleton pattern in Python?

Select 3 answers
A.They cannot be used with inheritance.
B.They introduce global state.
C.They cause difficulties in dependency injection.
D.They make unit testing more difficult.
E.They are always thread-safe.
AnswersB, C, D

A Singleton holds its instance in a shared, globally reachable location, so every consumer reads and writes the same state. This introduces global state, creating hidden coupling and making tests order-dependent and hard to isolate.

Why this answer

Option B is correct because a Singleton stores its single instance in a class-level or module-level location, effectively creating global mutable state that any part of the program can read or modify, which increases coupling and makes behavior harder to reason about. Option C is correct because Singleton clients typically call a hard-coded accessor such as getInstance() or reference a module-level instance directly, so you cannot easily substitute an alternative implementation through constructor injection or a DI container. Option D is correct because that same hard-coded access and shared state persist across tests, so tests can leak state into one another and cannot easily isolate the class under test with a mock or fake.

Option A is wrong because Singletons can participate in inheritance (subclasses can inherit or override the instance accessor), even if doing so is awkward. Option E is wrong because a naive Singleton implementation is not automatically thread-safe; without a lock or other synchronization, two threads can race and create multiple instances.

42
Multi-Selectmedium

A developer is designing a `Shape` base class and wants subclasses to be forced to implement an `area()` method. Which two approaches are valid in Python for enforcing that subclasses provide `area()`? (Choose two.)

Select 2 answers
A.Define `area` in the base class with a body that raises `NotImplementedError`, so subclasses fail at call time if they forget to override.
B.Inherit from `abc.ABC` and decorate `area` with `@abstractmethod`, so instantiation fails until the method is overridden.
C.Assign `area = None` in the base class body so calling `area()` raises TypeError automatically.
D.Decorate `area` with `@staticmethod` in the base class so subclasses must redefine it to avoid a TypeError.
E.Use `__slots__ = ('area',)` in the base class to declare `area` as a required slot that subclasses must fill.
AnswersA, B

Raising `NotImplementedError` in the base method is a widely used convention that surfaces missing overrides at runtime when the method is called. It does not prevent instantiation, but it does enforce that subclasses provide their own implementation to avoid the exception. This is valid though less strict than ABC-based enforcement.

Why this answer

Python enforces method implementation either through the `abc` module's `@abstractmethod` mechanism, which blocks instantiation until overrides exist, or through a base method that raises `NotImplementedError` at call time. The first is stricter and catches errors earlier; the second is a simpler convention that still signals missing overrides clearly.

Exam trap

The trap here is treating `@staticmethod` or `__slots__` as enforcement mechanisms, when neither imposes any obligation on subclasses to override a method.

43
MCQmedium

How do you implement the 'Strategy' pattern in Python?

A.By using a large switch-case statement.
B.By creating a metaclass for all strategies.
C.By passing strategy instances into the context constructor.
D.By using inheritance for every strategy.
AnswerC

Constructor injection lets the context hold a reference to an abstract strategy object, so algorithms are selected at runtime without subclassing the context. This satisfies the pattern's requirement of interchangeable behaviour, since swapping the passed instance changes the algorithm while the context's interface stays fixed.

Why this answer

By injecting different strategy objects into a context class, which then delegates operations to the injected object.

44
MCQmedium

When designing a class hierarchy, what is the best way to prevent a method from being overridden by subclasses?

A.Defining it as a private method.
B.Defining it as a static method.
C.Using the @final decorator from typing.
D.Using the 'final' keyword.
AnswerC

The @final decorator from typing marks the method so type checkers flag any subclass override, satisfying the requirement to prevent overriding. At runtime it does not enforce, but it is the documented, idiomatic mechanism for declaring a method non-overridable in a hierarchy.

Why this answer

The @final decorator from the typing module (PEP 591) is the standard, type-checker-enforced way to mark a method as non-overridable in Python. When applied to a method, static type checkers like mypy or Pyright will flag any subclass that attempts to override it, and applying @final to a class prevents subclassing entirely. Python itself does not enforce this at runtime, but the decorator is the idiomatic, documented mechanism for expressing the intent.

Exam trap

PCPP1 often tests the misconception that Python has a 'final' keyword like Java or C#, or that private (double-underscore) methods are truly non-overridable — candidates must recognize that @final from typing is the correct, PEP 591-sanctioned mechanism.

How to eliminate wrong answers

Option A is wrong because a private method (name-mangled with a leading double underscore) is only hidden from subclasses via name mangling — a subclass can still define a method with the same name and effectively override behavior, and Python does not prevent this. Option B is wrong because static methods are simply methods that don't receive self or cls; they can be overridden by subclasses just like any other method, so they provide no protection. Option D is wrong because 'final' is not a Python keyword — Python has no built-in final keyword; finality is expressed via the typing.final decorator or the @final annotation.

45
MCQeasy

A developer wants to ensure that a method `area` in a base class `Shape` must be overridden by any concrete subclass. The developer writes `class Shape(ABC):` and `@abstractmethod def area(self): ...`. A colleague then creates `class Circle(Shape):` without implementing `area` and tries to instantiate `Circle()`. What happens?

A.`Circle()` succeeds and `area()` returns `None` because the base method body is `...`.
B.A `TypeError` is raised at instantiation time because `Circle` still has abstract methods.
C.`Circle()` succeeds, and calling `area()` later raises `NotImplementedError`.
D.A `SyntaxError` is raised when the `Circle` class definition is parsed.
AnswerB

`ABCMeta` tracks unimplemented abstract methods. When `Circle` does not override `area`, its `__abstractmethods__` set still contains `'area'`, and calling `Circle()` triggers `TypeError: Can't instantiate abstract class Circle with abstract method area`. This is exactly the enforcement `@abstractmethod` provides.

Why this answer

The `ABCMeta` metaclass maintains a set of abstract method names on each class. A subclass that does not override all of them keeps a non-empty set, and instantiation raises `TypeError`. The `...` body is irrelevant because the abstract method is never invoked; enforcement happens before the object exists.

Exam trap

The trap here is assuming the abstract method body runs and raises `NotImplementedError`, when actually instantiation is blocked by the metaclass.

46
MCQmedium

You are designing a class `Connection` that must ensure only one instance exists and that the same instance is returned whenever `Connection()` is called. You implement this using `__new__` to control instance creation, and you also define `__init__`. During testing, you notice that `__init__` runs every time `Connection()` is called, even though `__new__` returns the existing singleton instance. What is the most appropriate way to prevent `__init__` from reinitializing the singleton on subsequent calls?

A.Delete the `__init__` method so only `__new__` runs.
B.Use a metaclass to override `__call__` and cache the instance.
C.Override `__new__` to return `None` when an instance already exists.
D.Use a class-level flag to track initialization and guard the `__init__` logic.
AnswerD

Python always calls `__init__` on the object returned by `__new__` if it is an instance of the class. A class-level boolean flag set after the first initialization lets `__init__` skip reinitialization on later calls, preserving the singleton's state. This is the standard, explicit way to prevent repeated initialization while still returning the same object.

Why this answer

When `__new__` returns an existing instance, Python still calls `__init__` on it. To avoid reinitialization, the class must explicitly track whether initialization has already occurred. A class-level flag checked at the start of `__init__` is the simplest, most readable solution.

It preserves the singleton's state and avoids side effects from repeated setup, which is critical for resources like connections.

Exam trap

The trap here is assuming that returning an existing instance from `__new__` prevents `__init__` from running again.

47
MCQhard

A developer defines `class Config:` with `__slots__ = ('host', 'port')`. Later, they want to add a new attribute `timeout` to an existing instance without modifying the class. They write `cfg.timeout = 30` and receive an `AttributeError`. Which change would allow per-instance `timeout` assignment while keeping `__slots__` for `host` and `port`?

A.Add `'__dict__'` to `__slots__`.
B.Add a `__getattr__` method that returns `30` for unknown names.
C.Subclass `Config` and add `__slots__ = ()` in the subclass.
D.Change `__slots__` to a dictionary mapping names to default values.
AnswerA

Including `'__dict__'` in `__slots__` reserves a per-instance dictionary in addition to the slot descriptors. Instances still get fast slot storage for `host` and `port`, but also accept arbitrary new attributes like `timeout`. This is the documented way to opt back into dynamic attributes while retaining slots for the declared names.

Why this answer

Slot descriptors are the only writable attributes unless the class also provides a `__dict__`. Declaring `'__dict__'` inside `__slots__` creates that dictionary, so new names like `timeout` can be stored per instance while `host` and `port` remain fast slots. The other options either do not affect assignment or misuse the `__slots__` contract.

Exam trap

The trap here is thinking `__getattr__` or a subclass trick enables assignment, when only adding `__dict__` to `__slots__` restores dynamic attributes.

48
MCQmedium

How do you correctly call a method from a sibling class in a diamond inheritance structure using super()?

A.Call the grandparent class directly.
B.Use super() in each class to delegate to the next class in the MRO.
C.Use the __bases__ attribute to iterate through parents.
D.Explicitly call the sibling class method by name.
AnswerB

Using `super()` in each class delegates along Python's C3 linearisation, so every sibling in the diamond is invoked exactly once in method resolution order. This satisfies the stem's requirement to reach a sibling class without duplicating the shared base, which direct base-class calls would trigger.

Why this answer

super() follows the Method Resolution Order (MRO), which correctly handles diamond inheritance by delegating calls to the next class in the chain, not just the parent.

49
Multi-Selectmedium

Which TWO of the following are correct regarding the 'with' statement in Python?

Select 2 answers
A.It works by implicit __init__ calling.
B.It only works with file objects.
C.It can be used with any object.
D.It requires the class to define __enter__ and __exit__.
E.It guarantees that __exit__ is called even if an exception occurs.
AnswersD, E

The context manager protocol requires the class to implement __enter__, invoked on entry to the with block, and __exit__, invoked on exit. Without both methods defined, Python raises AttributeError, so this is a genuine requirement of the with statement's protocol.

Why this answer

Option D is correct because the context manager protocol requires the object used in a 'with' statement to implement the special methods __enter__() (which runs on entry and may return a value bound via 'as') and __exit__() (which runs on exit and receives exception type, value, and traceback). Option E is correct because the 'with' statement is semantically equivalent to a try/finally block, so __exit__() is invoked whether the block completes normally or raises an exception, ensuring cleanup such as closing files or releasing locks. Option A is wrong because 'with' does not rely on __init__; initialization happens when the object is constructed, not when the context is entered.

Option B is wrong because 'with' is not limited to file objects; any object implementing the context manager protocol works, including locks, sockets, and database connections. Option C is wrong as stated because not every object supports the protocol — only those defining __enter__ and __exit__ can be used directly in a 'with' statement.

50
MCQmedium

In the context of the Factory pattern, what does a concrete factory implement?

A.The registration logic for all factories.
B.The creation interface for specific product variants.
C.The logic for the singleton pattern.
D.The product logic.
AnswerB

A concrete factory implements the abstract creation interface declared by the factory, returning concrete product instances for a specific variant. This lets client code request products through the shared interface while remaining decoupled from the concrete classes.

Why this answer

A concrete factory implements the interface defined by the abstract factory to produce specific products.

51
MCQmedium

When implementing the Singleton pattern, what is the advantage of using a module-level instance over a class-based Singleton?

A.It is more secure.
B.It allows multiple instances if needed.
C.It is simpler and leverages Python's built-in module caching mechanism.
D.It supports lazy loading.
AnswerC

Python caches imported modules in sys.modules, so a module-level instance is created once at import and shared thereafter. This gives singleton behaviour with no metaclass, __new__ override or locking, satisfying the pattern with less code.

Why this answer

Python modules are naturally Singletons because they are initialized only once upon the first import.

52
MCQeasy

What is the purpose of the __repr__ method?

A.To be called by print().
B.User-friendly output.
C.To store the object in memory.
D.Unambiguous representation for debugging.
AnswerD

The `__repr__` method returns an unambiguous string representation of an object, intended primarily for developers and debugging. Unlike `__str__`, which prioritises readability for end users, `__repr__` should ideally produce output that could recreate the object, satisfying the requirement for precise, diagnostic output.

Why this answer

It provides a formal, unambiguous string representation of an object, ideally suitable for re-creating the object.

53
MCQhard

Why does the 'abc' module provide the 'ABCMeta' metaclass?

A.To allow private methods.
B.To improve method resolution speed.
C.To enforce abstract method implementation at instantiation.
D.To support multiple inheritance.
AnswerC

ABCMeta intercepts instantiation of classes using abstractmethod, raising TypeError when any abstract method remains unimplemented. This enforces the contract at object creation time, rather than merely documenting it, which is the enforcement the abc module exists to provide.

Why this answer

ABCMeta is the metaclass that implements the logic to prevent instantiation of classes with abstract methods.

54
MCQhard

You are implementing a class that represents a monetary amount. You want the expression amount1 + amount2 to return a new Money instance, and you also want amount1 + 5 to work by treating 5 as a Money with zero cents. However, 5 + amount1 should raise a TypeError. Which implementation achieves this behavior?

A.Define __add__(self, other) and __radd__(self, other) where __radd__ simply calls self.__add__(other).
B.Define __iadd__(self, other) to handle both Money and int, and rely on Python to fall back to __add__ for the + operator.
C.Define __add__(self, other) to handle both Money and int, and define __radd__ to raise TypeError.
D.Define only __add__(self, other) to handle Money and int, and do not define __radd__.
AnswerD

When evaluating 5 + amount1, Python first tries int.__add__(5, amount1), which returns NotImplemented because int does not know Money. It then tries Money.__radd__(amount1, 5). If __radd__ is not defined, Python raises TypeError. Meanwhile amount1 + amount2 and amount1 + 5 work via __add__. This matches the required behavior exactly.

Why this answer

For amount1 + amount2 and amount1 + 5, Python calls Money.__add__, which can handle both Money and int. For 5 + amount1, int.__add__ returns NotImplemented, and Python then looks for Money.__radd__. Since __radd__ is not defined, a TypeError is raised.

This precisely implements the asymmetric addition behavior required.

Exam trap

The trap here is thinking that defining __radd__ is necessary for all addition, when omitting it is exactly what causes the reflected operation to fail as required.

55
Multi-Selecthard

Which THREE of the following represent common issues when using __slots__?

Select 3 answers
A.Subclasses must define their own __slots__ to avoid creating a __dict__.
B.They make it impossible to add new attributes dynamically.
C.They are incompatible with all decorators.
D.They prevent the use of multiple inheritance.
E.Classes with __slots__ cannot have a __dict__ by default.
AnswersA, B, E

Without redeclaring __slots__ in the subclass, Python reinstates a __dict__ on instances, silently negating the memory savings the parent achieved. This is the classic inheritance pitfall, so subclasses must repeat the declaration to preserve the slot-only layout.

Why this answer

Option A is correct because if a subclass does not declare its own __slots__, Python automatically gives it a __dict__, defeating the memory-saving purpose of __slots__ in the parent class. Option B is correct because __slots__ restricts instances to only the named attributes, so assigning any new attribute not listed raises an AttributeError. Option E is correct because defining __slots__ removes the per-instance __dict__ by default, which is the primary mechanism for saving memory.

Option C is wrong because __slots__ works fine with most decorators such as @property, @staticmethod, and @classmethod. Option D is wrong because __slots__ does not prevent multiple inheritance; it only imposes constraints when multiple bases have non-empty __slots__ with conflicting slot names.

56
MCQmedium

You are implementing the Observer pattern. Why should you avoid using a strong reference to the observers in the subject's list?

A.It causes circular dependency issues.
B.It makes the notify method thread-unsafe.
C.It violates the principle of encapsulation.
D.It prevents the observer from being garbage collected when it is no longer needed.
AnswerD

A strong reference held by the subject keeps each observer alive, so reference counting never reaches zero and the garbage collector cannot reclaim observers that are no longer needed. Weak references let them be collected once external references disappear.

Why this answer

Strong references prevent garbage collection of the observers, leading to memory leaks if observers are meant to be temporary.

57
MCQeasy

What is the primary difference between @classmethod and @staticmethod?

A.@staticmethod can access instance variables.
B.@classmethod receives the class as the first argument; @staticmethod does not.
C.@classmethod is for private methods.
D.@classmethod is slower.
AnswerB

A classmethod binds to the class and passes it implicitly as the first argument (conventionally `cls`), so it can access or modify class state and works correctly with inheritance. A staticmethod receives no implicit first argument, behaving as a plain function namespaced inside the class. This directly satisfies the stem's request for the primary distinction.

Why this answer

@classmethod receives the class as the first argument, while @staticmethod receives no implicit class or instance argument.

58
MCQeasy

Which magic method enables index access (e.g., obj[i])?

A.__getitem__
B.__index__
C.__setitem__
D.__access__
AnswerA

`__getitem__` is the magic method Python invokes for subscription syntax, so defining it lets instances respond to `obj[i]` with square brackets. It satisfies the stem's index-access constraint directly, unlike `__getattr__`, which handles attribute lookup, or `__iter__`, which drives iteration protocols.

Why this answer

The __getitem__ method allows an object to support indexing.

59
Multi-Selectmedium

Which TWO of the following are true about magic methods?

Select 2 answers
A.They are only available for built-in types.
B.They provide hooks into Python language operators.
C.They always start and end with '__'.
D.They cannot be overridden.
E.They are intended for direct calling by the user.
AnswersB, C

Magic methods such as __add__, __eq__ and __len__ are invoked implicitly by Python's syntax and built-in operations, letting user classes integrate with operators, iteration and context managers. This hook mechanism is exactly what the stem asks about, rather than naming conventions or inheritance behaviour.

Why this answer

Option B is correct because magic methods (also called dunder methods) act as hooks that let user-defined classes integrate with Python's built-in syntax and operators — for example, __add__ is invoked by the + operator, __len__ by len(), and __iter__ by iteration. Option C is correct because magic methods are conventionally and syntactically named with a leading and trailing double underscore, such as __init__, __str__, or __eq__. Option A is wrong because magic methods are not limited to built-in types; user-defined classes can define and override them.

Option D is wrong because magic methods can be overridden in subclasses or custom classes to customize behavior. Option E is wrong because magic methods are meant to be invoked implicitly by the interpreter through operators and built-in functions, not called directly by the user.

60
Multi-Selectmedium

Which TWO of the following are valid ways to create a singleton in Python?

Select 2 answers
A.Using a global variable that is not initialized.
B.Using a function that returns a new instance every call.
C.Using a base class with a custom __new__ method.
D.Using a standard class without any special methods.
E.Using a module as a singleton.
AnswersC, E

Overriding `__new__` in a base class lets you intercept instantiation before `__init__` runs, returning the same cached instance on every call. This satisfies the singleton constraint by centralising instance control in one reusable parent, so subclasses inherit the behaviour without duplicating logic.

Why this answer

Option C is correct because overriding __new__ in a base class lets you intercept instance creation and return the same cached instance every time, which is the canonical Python idiom for enforcing a single instance. Option E is correct because Python modules are imported only once and cached in sys.modules, so module-level state and functions naturally behave as a singleton. Option A is wrong because an uninitialized global variable provides no instance at all and no control over creation.

Option B is wrong because returning a new instance on every call is the opposite of the singleton pattern. Option D is wrong because a plain class with no special methods creates a fresh object on each instantiation.

61
MCQmedium

You are designing a plugin registry that must automatically register every concrete subclass of a base class `Plugin` at the moment the subclass is defined, without requiring any explicit registration call in the subclass body. You want to avoid a metaclass because the project already uses `ABCMeta` for the base class and you do not want to introduce metaclass conflicts. Which approach best satisfies this requirement?

A.Use `__subclasses__()` on `Plugin` inside a periodic background thread and append any newly discovered subclasses to the registry.
B.Override `__set_name__` on `Plugin` so that each subclass reports its name to the registry when the class body finishes executing.
C.Define `__init_subclass__` as a classmethod on `Plugin` that appends the newly created class to a registry list.
D.Define `__new__` on `Plugin` and insert the class into the registry whenever `Plugin` itself is instantiated.
AnswerC

`__init_subclass__` is an implicit classmethod invoked on the parent class immediately after each subclass is created, receiving the new class as its first argument. It runs automatically at class-definition time, so no explicit registration call is needed in the subclass body. Because it is just a normal method, it composes cleanly with `ABCMeta` instead of replacing or conflicting with it.

Why this answer

The implicit `__init_subclass__` classmethod is executed by Python on the parent class right after a subclass is created, so it provides exactly the definition-time hook the registry needs. It is a plain method, so it coexists with `ABCMeta` and other metaclasses without conflict. Hooks tied to instantiation or descriptor binding run at the wrong moment and cannot reliably capture every concrete or abstract subclass.

Exam trap

The trap here is confusing hooks that run at class-definition time with hooks like `__new__` that only run when an instance is constructed.

62
MCQeasy

What happens when you add two objects of a class that implements __add__?

A.It does nothing.
B.It concatenates the objects.
C.It raises a TypeError.
D.The __add__ method is called.
AnswerD

Python's binary operator protocol dispatches addition to the left operand's type: evaluating a + b invokes type(a).__add__(a, b). Since the class defines __add__, that method executes, receiving self and the other operand, and its return value becomes the expression's result.

Why this answer

The __add__ method is automatically called by the interpreter when the '+' operator is used.

63
MCQhard

If you define a class with a metaclass that has a custom __call__ method, when is that __call__ method executed?

A.Every time an instance method is called.
B.When an instance of the class is created.
C.When the metaclass is first imported.
D.When the class itself is defined.
AnswerB

A metaclass's `__call__` intercepts instantiation of the class it builds, running when you call the class to create an instance. It satisfies the stem's constraint by executing at instance-creation time, before `__init__` and `__new__` of the class itself, allowing the metaclass to customise or replace the returned object.

Why this answer

The metaclass's __call__ method is invoked whenever the class created by that metaclass is instantiated (i.e., when you call the class name like MyClass()).

64
MCQeasy

Which method is used to customize the behavior of the 'in' operator?

A.__contains__
B.__has__
C.__find__
D.__in__
AnswerA

Defining `__contains__` on a class lets Python delegate membership tests to it, so `x in instance` invokes your logic instead of falling back to iteration via `__iter__` or `__getitem__`. This directly customises the `in` operator's behaviour, satisfying the stem's requirement precisely.

Why this answer

The __contains__ method is called when using the 'in' or 'not in' operators.

65
MCQeasy

Which magic method should you implement to make your objects support the 'with' statement context manager?

A.__context__
B.__enter__ and __exit__
C.__open__ and __close__
D.__init__ and __del__
AnswerB

The with statement calls __enter__ on entry and __exit__ on exit, the latter receiving exception details for cleanup or suppression. Implementing both satisfies the stem's context manager requirement, since neither method alone enables the protocol.

Why this answer

The __enter__ and __exit__ methods are required to implement the context manager protocol.

66
MCQeasy

Which function is used to check if an object is an instance of a class?

A.hasattr()
B.issubclass()
C.type()
D.isinstance()
AnswerD

`isinstance()` accepts an object and a class (or tuple of classes) and returns a Boolean, resolving the object's type through its class hierarchy, including inherited and virtual subclasses. This directly satisfies the stem's requirement to check whether an object is an instance of a class, unlike identity or equality comparisons.

Why this answer

isinstance(obj, Class) is the standard way to check inheritance.

67
MCQmedium

In the context of the Factory pattern in Python, what is a typical benefit of using a registry-based approach instead of a large if-else block?

A.It guarantees thread safety without locks.
B.It prevents the instantiation of abstract classes.
C.It enables easy extension by adding new products without changing the factory.
D.It significantly reduces memory usage.
AnswerC

A registry maps product identifiers to creator callables, so new products register themselves at import time. This satisfies the open/closed constraint: the factory function stays untouched, eliminating the if-else chain that would otherwise require editing whenever a product type is added.

Why this answer

A registry-based factory stores a mapping (typically a dict) from product identifiers to their concrete classes or creator callables, so the factory simply looks up and instantiates the requested product. Adding a new product means registering it in the mapping — no modification to the factory's control flow is required, satisfying the Open/Closed Principle. This decouples the factory from concrete product classes and makes extension a configuration concern rather than a code change.

Exam trap

PCPP1 often tests whether candidates confuse the extensibility benefit of the registry pattern with unrelated concerns like thread safety or memory optimization, luring them toward plausible-sounding but technically unrelated options.

How to eliminate wrong answers

Option A is wrong because registry lookups are not inherently thread-safe — concurrent registration or mutation of the registry dict still requires locks or a thread-safe structure like a lock-protected dict or concurrent mapping. Option B is wrong because preventing abstract class instantiation is the job of abc.ABCMeta/abstractmethod or raising NotImplementedError, not of the registry pattern itself. Option D is wrong because a registry adds a dict of references and does not meaningfully reduce memory usage; memory footprint is dominated by the product instances, not the lookup mechanism.

68
MCQhard

Why might you use the __init_subclass__ hook instead of a metaclass?

A.Metaclasses are deprecated.
B.Metaclasses cannot be used for registration.
C.It is faster at runtime.
D.It is easier to implement and avoids metaclass conflicts.
AnswerD

`__init_subclass__` runs automatically on subclass creation without a separate metaclass, so it satisfies the stem's need to hook subclassing while avoiding metaclass conflicts. It is a plain classmethod on the parent, simpler to implement than a metaclass, and coexists with any existing metaclass.

Why this answer

__init_subclass__ provides a simpler way to perform logic when a subclass is created, without the complexity and inheritance issues associated with custom metaclasses.

Ready to test yourself?

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