PCPP1 study materials
PCPP1 study resources

PCPP1 practice questions

One question at a time. Build your PCPP1 understanding.

First 50 of 200 questions. Each one lists the choices, the correct answer, and an explanation. The checker at the top of this page saves progress in this browser when it is available.

Continue in the practice tool above to load more questions, 50 at a time.

Question 1Advanced OOP

In Python's data model, which special method is invoked by the expression obj[index] when obj is an instance of a user-defined class?

  1. __index__
  2. __get__
  3. __getattr__
  4. __getitem__ Correct answer.

Explanation

Subscript syntax obj[index] is translated by the interpreter into a call to the type's __getitem__ method, receiving both the instance and the key. __getattr__ is only consulted when normal attribute lookup fails, and __get__ implements the descriptor protocol rather than subscripting.

Question 2Advanced OOP

A class defines __init__ to set instance attributes. Which statement best describes when this method runs relative to object creation?

  1. It runs only when the class is first imported into a module
  2. It runs each time the class body is executed by the interpreter
  3. It runs after the instance is created by __new__, and its return value is discarded Correct answer.
  4. It runs before __new__ and its return value becomes the instance

Explanation

__new__ is responsible for producing the raw instance, and __init__ is then called on that instance to initialize it. Because __init__ initializes an existing object, returning anything other than None from it raises TypeError.

Question 3Advanced OOP

Which statement about Python's method resolution order for a class using multiple inheritance is correct?

  1. It raises an error whenever two bases share a common ancestor
  2. It searches the most recently defined base first, regardless of declaration order
  3. It always searches bases strictly left to right, ignoring ancestor ordering
  4. It follows C3 linearization, so a subclass always precedes its bases and a base precedes its own ancestors Correct answer.

Explanation

Python computes the MRO with the C3 linearization algorithm, which preserves local precedence order and monotonicity across the inheritance graph. A shared ancestor is therefore legal; it simply appears once, after all classes that inherit from it.

Question 4Advanced OOP

A class defines both __str__ and __repr__. Which method is used when an instance is placed inside a list and the list is printed?

  1. The method defined last in the class body
  2. Neither; the default object representation is always used inside containers
  3. __str__, because printing always prefers __str__ recursively
  4. __repr__, because containers render their elements using repr Correct answer.

Explanation

The list's own __str__ delegates to repr on its elements, so a custom __repr__ controls how instances appear inside containers. __str__ is used only when str() is called directly on the object, such as with print(obj).

Question 5Advanced OOP

Which class declaration makes Employee an abstract base class whose subclasses must implement a method named compute_pay?

  1. class Employee(Protocol): with @staticmethod decorating compute_pay
  2. class Employee(Generic): with @property decorating compute_pay
  3. class Employee(ABC): with @abstractmethod decorating compute_pay Correct answer.
  4. class Employee(metaclass=ABCMeta): with @classmethod decorating compute_pay

Explanation

Subclassing ABC and decorating the required method with @abstractmethod prevents instantiation until every abstract method is overridden. ABCMeta can also be used directly, but the decorator must be @abstractmethod for the enforcement to apply.

Question 6Advanced OOP

A class declares __slots__ = ('x', 'y'). What is the practical effect on instances of that class?

  1. Instances gain no __dict__ by default, so assigning other attributes raises AttributeError Correct answer.
  2. The attributes x and y become read-only after initialization
  3. Instances can hold any attribute, but lookups become slower
  4. The class becomes abstract and cannot be instantiated

Explanation

__slots__ reserves fixed storage for the listed names and suppresses the per-instance __dict__, which reduces memory use. Attribute assignment to names outside the slots then fails unless a base class still supplies a __dict__.

Question 7Advanced OOP

A class defines a descriptor on the class with only a __get__ method. What happens when code attempts to assign a new value to that attribute on an instance?

  1. The assignment fails with AttributeError because the descriptor is non-data and shadows only reads
  2. The assignment invokes __set_name__ on the descriptor
  3. The descriptor's __get__ is called again and the value is stored in the class
  4. The assignment silently creates an instance attribute, because non-data descriptors yield to the instance dictionary Correct answer.

Explanation

A descriptor implementing only __get__ is a non-data descriptor, so instance attribute lookup takes precedence over it and assignment simply writes into the instance __dict__. Data descriptors, which define __set__ or __delete__, take precedence over the instance dictionary.

Question 8Advanced OOP

Which pair of methods should a class implement so that instances support the in operator and iteration with a for loop over the same underlying data?

  1. __next__ and __enter__
  2. __getitem__ and __len__
  3. __contains__ and __iter__ Correct answer.
  4. __call__ and __index__

Explanation

The in operator consults __contains__, and for loops call __iter__, so defining both gives explicit control over membership tests and iteration. Without __contains__, Python falls back to iterating, but implementing it directly is the intended design.

Question 9Advanced OOP

What is the purpose of super() inside a method of a class that participates in multiple inheritance?

  1. It creates a new instance of the parent class and calls the method on it
  2. It delegates to the next class in the instance's method resolution order, not strictly to the direct parent Correct answer.
  3. It always calls the method on the class named in the class definition's first base
  4. It bypasses the MRO and invokes the method defined on object

Explanation

super() resolves to the next class after the current one in the MRO of the actual instance type, which is what makes cooperative multiple inheritance work. It does not simply mean 'the parent class named in the source'.

Question 10Advanced OOP

Which statement about the relationship between a class attribute and an instance attribute of the same name is correct?

  1. Class attributes are copied into every instance at construction time
  2. An instance attribute always hides the class attribute even before it is assigned
  3. Assigning on the instance mutates the class attribute for all instances
  4. Reading an instance attribute falls back to the class attribute, but assigning on the instance creates a separate entry Correct answer.

Explanation

Attribute lookup checks the instance dictionary first and then the class, so reads see the class value until an instance assignment shadows it. The assignment writes into the instance __dict__ and leaves the class attribute untouched.

Question 11Advanced OOP

A developer wants a property named balance that validates values on assignment. Which arrangement implements this correctly?

  1. Define balance with @property for the getter and @balance.setter for the validating setter Correct answer.
  2. Define balance with @classmethod and store the value on the class
  3. Define balance with @staticmethod and validate inside __init__ only
  4. Define balance with @property and a separate method named set_balance

Explanation

The @property decorator creates the getter, and pairing it with @balance.setter registers a setter that runs on every assignment to that attribute. A separate set_balance method would not be triggered by attribute assignment syntax.

Question 12Advanced OOP

Which statement correctly distinguishes @staticmethod from @classmethod?

  1. A static method receives neither self nor cls, while a class method receives the class as its first argument Correct answer.
  2. A static method receives the class, while a class method receives the instance
  3. Both receive the instance, but only static methods can be inherited
  4. A class method cannot be called on an instance, only on the class itself

Explanation

A static method is an ordinary function placed in the class namespace and receives no implicit argument. A class method is bound to the class, receiving cls, and can be invoked through either the class or an instance.

Question 13Advanced OOP

A class implements __eq__ to compare instances by an id field but does not implement __hash__. What is the consequence for using those instances as dictionary keys?

  1. Instances are automatically hashed from the id field by the interpreter
  2. Instances remain hashable using identity, and equal instances may occupy separate dict entries
  3. The class becomes unhashable, so using an instance as a dict key raises TypeError Correct answer.
  4. Instances can be used as keys only if __ne__ is also defined

Explanation

Defining __eq__ sets __hash__ to None in the class, making instances unhashable unless __hash__ is defined explicitly. The invariant that equal objects must share a hash is why Python removes the inherited identity hash.

Question 14Advanced OOP

Which protocol makes an object usable in a with statement, and which methods must it provide?

  1. The callable protocol, requiring __call__
  2. The iterator protocol, requiring __iter__ and __next__
  3. The context manager protocol, requiring __enter__ and __exit__ Correct answer.
  4. The descriptor protocol, requiring __get__ and __set__

Explanation

The with statement calls __enter__ on entry and guarantees __exit__ is called on exit, including when exceptions propagate. __exit__ receives exception details and can suppress an exception by returning a truthy value.

Question 15Advanced OOP

What does the __set_name__ method on a descriptor receive, and when is it called?

  1. The owning class and the attribute name, called once when the owning class is created Correct answer.
  2. The instance and the new value, called on every assignment
  3. The metaclass and the method resolution order, called on first instantiation
  4. The descriptor itself and the module name, called at import of the descriptor's module

Explanation

During class creation, Python calls __set_name__ on each descriptor found in the class body, passing the class and the name it was bound to. This lets descriptors learn their own attribute name without the name being repeated in the constructor call.

Question 16Advanced OOP

Which statement about the __init_subclass__ hook is accurate?

  1. It replaces __init__ when a class uses multiple inheritance
  2. It is a class method implicitly, called on the parent when a subclass is defined Correct answer.
  3. It is called on each instance right after __init__ completes
  4. It is called only when a subclass overrides __new__

Explanation

__init_subclass__ runs on the parent class at the moment a subclass is created, which makes it useful for registering or validating subclasses. It is implicitly a class method, so the new subclass is passed as the first argument.

Question 17Advanced OOP

A class wants instances to be callable so that obj(3) returns a computed value. Which method must be defined?

  1. __invoke__
  2. __apply__
  3. __call__ Correct answer.
  4. __func__

Explanation

Defining __call__ on a class makes its instances callable, with the instance passed as the first argument followed by the call arguments. This is how callable objects such as stateful functions or functors are built.

Question 18Advanced OOP

Which statement best explains why a mixin class is typically placed first in a base class list such as class Service(LoggingMixin, Base)?

  1. The order only affects attribute assignment, not method lookup
  2. Placing the mixin first prevents it from being inherited by subclasses
  3. Earlier bases appear earlier in the MRO, so the mixin's methods take precedence over Base's Correct answer.
  4. Python requires mixins to be listed before any concrete base or it raises TypeError

Explanation

Method lookup follows the MRO, and the C3 linearization preserves the declared order of bases where constraints allow, so a leading mixin can override methods from later bases. Cooperative super() calls then let the mixin augment rather than replace the base behavior.

Question 19Advanced OOP

What is the effect of calling an abstract base class's register method with a class argument?

  1. It prevents the registered class from being instantiated
  2. It copies the abstract methods into the registered class
  3. It creates a subclass that inherits all concrete methods
  4. It makes isinstance and issubclass treat that class as a virtual subclass without inheritance Correct answer.

Explanation

register performs virtual subclass registration, so isinstance and issubclass checks succeed even though the class does not inherit from the ABC. No methods are copied and the registered class's behavior is unchanged.

Question 20Advanced OOP

Which statement about the __del__ finalizer is correct?

  1. It is guaranteed to run before interpreter shutdown for every object
  2. It is called when an object is about to be reclaimed, and its timing is not guaranteed in all implementations Correct answer.
  3. It is called explicitly by the garbage collector before __init__ for reused objects
  4. It is called deterministically when the last reference is dropped, before memory is freed

Explanation

__del__ is invoked when the object is being destroyed, but CPython's reference counting and the cyclic garbage collector make the exact timing implementation-dependent. Objects in reference cycles or surviving until shutdown may never have __del__ called reliably.

Question 21Advanced OOP

What does the __class__ attribute of an instance refer to, and what happens when it is reassigned between compatible classes?

  1. It refers to the parent class, and reassignment triggers __init__ again automatically
  2. It refers to the metaclass, and reassignment changes the metaclass of all instances
  3. It refers to the class body namespace, and reassignment has no observable effect
  4. It refers to the type of the instance, and reassignment changes the type used for subsequent attribute lookup Correct answer.

Explanation

The __class__ attribute exposes the instance's type, and assigning a different class changes which class is consulted during attribute lookup. This is a low-level operation that bypasses __init__ and can leave the instance in an inconsistent state if layouts differ.

Question 22Advanced OOP

Which statement about operator overloading with __add__ is correct?

  1. Returning NotImplemented raises TypeError immediately without trying other operands
  2. It is only consulted when both operands are exactly the same type
  3. It should return a new object rather than mutating self, and returning NotImplemented allows the reflected operation to be tried Correct answer.
  4. It may mutate self freely, since augmented assignment operators are the same as binary operators

Explanation

__add__ implements binary + and conventionally produces a new value, while __iadd__ handles +=. Returning NotImplemented signals that the operation is unsupported for those operands so Python can try the right operand's reflected method.

Question 23Advanced OOP

A class defines __len__ to return a negative value. What happens when len() is called on an instance?

  1. The absolute value of the result is returned
  2. A TypeError is raised because __len__ must return a float
  3. The negative value is returned unchanged
  4. A ValueError is raised because __len__ must return a non-negative integer Correct answer.

Explanation

The interpreter enforces that __len__ returns a non-negative integer and raises ValueError otherwise. This constraint keeps len() consistent with truthiness testing and with protocols that use length information.

Question 24Advanced OOP

Which statement about the relationship between type() and a user-defined metaclass is correct?

  1. Every class's metaclass is type, regardless of the metaclass keyword
  2. A metaclass can only be applied to abstract base classes
  3. A metaclass must inherit from object rather than from type
  4. A class whose metaclass is Meta has type(cls) equal to Meta, and type is itself the default metaclass Correct answer.

Explanation

Classes are themselves instances of their metaclass, so type(cls) returns the metaclass, and type serves as the default when none is specified. Custom metaclasses normally subclass type and can intercept class creation via __new__ or __init__.

Question 25Advanced OOP

In a class hierarchy, a subclass overrides a method and calls super().method() inside it. Which statement describes the resulting behavior in a diamond hierarchy with cooperative methods?

  1. The common base method runs once per path, producing duplicate side effects
  2. super() raises an error when the hierarchy contains a common ancestor
  3. Each class in the MRO runs its own version exactly once, because super() advances along the shared MRO Correct answer.
  4. The subclass method runs twice because two bases are present

Explanation

In cooperative multiple inheritance, each method calls super(), which advances to the next class in the instance's MRO, so the shared base is reached once. This is why cooperative methods should accept and forward keyword arguments consistently.

Question 26Advanced OOP

A class defines both __getattr__ and __getattribute__. Under what circumstance is __getattr__ invoked for an attribute lookup on an instance?

  1. Only when __getattribute__ raises AttributeError for that name Correct answer.
  2. Whenever the attribute exists on the class but not on the instance
  3. Only when the attribute name begins with a single underscore
  4. Before __getattribute__, so it can short-circuit the lookup

Explanation

__getattribute__ runs on every attribute access and normally raises AttributeError when the name is not found; __getattr__ is then called as a fallback hook. If __getattribute__ succeeds, __getattr__ is never reached. This makes __getattr__ suitable for dynamic fallback values rather than general interception.

Question 27Advanced OOP

A class defines __iter__ to return self and __next__ to raise StopIteration when exhausted. What limitation does such an object share with a generator-based iterator?

  1. It must also define __getitem__ for the for loop to work
  2. It can be iterated only once because its position state lives in the object itself Correct answer.
  3. It cannot raise StopIteration from inside __next__
  4. It cannot be used in a for loop because it lacks __len__

Explanation

When the iterator is the iterable, the traversal position is stored on the instance. After exhaustion, another loop over that instance yields no values. Generator iterators behave the same way; calling a generator function again creates a fresh iterator rather than resetting an existing one.

Question 28Advanced OOP

A class defines __get__, __set__ and __delete__ and an instance of it is assigned to a class attribute. What happens when code reads that attribute through an instance of the owning class?

  1. The instance dictionary value shadows the descriptor
  2. The descriptor object itself is returned unchanged
  3. An AttributeError is raised because data descriptors are read-only
  4. The descriptor's __get__ runs and its return value is used Correct answer.

Explanation

A descriptor implementing __set__ or __delete__ is a data descriptor, which takes precedence over the instance dictionary. Attribute access therefore delegates to __get__, passing the instance and the owner class. The stored descriptor object is not returned.

Question 29Advanced OOP

Which statement describes what happens when a class body defines a method named __repr__ and also inherits __str__ from object?

  1. str() returns the default memory-address string, ignoring __repr__
  2. str() raises TypeError until __str__ is defined explicitly
  3. str() uses __repr__ only if __repr__ is also assigned to __str__
  4. str() falls back to __repr__ because object.__str__ delegates to it Correct answer.

Explanation

object.__str__ is implemented to call __repr__, so defining __repr__ alone gives consistent output for both str() and repr(). This is why a well-written __repr__ often suffices for debugging output. Defining __str__ separately is only needed for a friendlier display form.

Question 30Advanced OOP

A class defines __enter__ returning a resource and __exit__ returning None. An exception is raised inside the with block. What is the outcome?

  1. __exit__ runs and the exception is converted into a RuntimeError
  2. __exit__ runs and the exception is silently suppressed
  3. __exit__ is skipped and the exception propagates immediately
  4. __exit__ runs and the exception propagates after it returns Correct answer.

Explanation

A falsy return from __exit__ means the exception is not suppressed, so it continues propagating after cleanup runs. Returning True is what suppresses the exception. The __exit__ method always receives the exception type, value and traceback when one occurred.

Question 31Advanced OOP

Which statement about the __init__ method of an immutable-style class that defines __slots__ is accurate?

  1. It must return the newly created instance so the object is registered
  2. It cannot assign to slot names because slots are read-only after creation
  3. It is called before __new__ and therefore cannot use the instance
  4. It should assign attributes through object.__setattr__ only if __setattr__ is overridden to block writes Correct answer.

Explanation

Slot attributes are ordinary writable descriptors unless the class overrides __setattr__ to forbid assignment. In that case initialization must bypass the override, typically with object.__setattr__. Slots restrict which attributes exist, not whether they can be set.

Question 32Advanced OOP

A base class defines a classmethod named create that returns cls(). A subclass calls Sub.create(). Which class does the returned object belong to?

  1. The base class, because the method body is defined there
  2. object, because cls is resolved at definition time
  3. Whichever class is first in the method resolution order
  4. Sub, because cls is bound to the class the call was made on Correct answer.

Explanation

A classmethod receives the class through which it was invoked, including subclasses, so cls is Sub in this call. This is the standard idiom for alternative constructors that respect inheritance. A staticmethod would instead need the class passed explicitly.

Question 33Advanced OOP

Which statement about the method resolution order produced by the C3 linearization algorithm is correct?

  1. The order ignores classes that appear more than once in the hierarchy
  2. A class always appears before its parents, and the order is consistent with every declared base ordering Correct answer.
  3. The order is determined solely by the order in which methods were defined in each class body
  4. The order places the most derived class last so that base implementations run first

Explanation

C3 linearization guarantees that each class precedes its bases and that the relative order of bases in each declaration is preserved. Python raises TypeError at class creation when no consistent order exists. Duplicate bases are merged rather than ignored.

Question 34Advanced OOP

A class implements __eq__ comparing instances by a code attribute. Which additional definition keeps instances usable as dictionary keys while remaining consistent with equality?

  1. __hash__ returning id(self)
  2. __hash__ returning hash(self.code) Correct answer.
  3. No definition is needed because object.__hash__ remains active
  4. __hash__ returning a constant value for all instances

Explanation

Equal objects must have equal hashes, so the hash should derive from the same field used by __eq__. Returning id(self) or a constant breaks the contract or degrades lookup performance. Defining __eq__ without __hash__ sets __hash__ to None, making instances unhashable.

Question 35Advanced OOP

What distinguishes the behavior of a property decorated with a setter from a plain class attribute of the same name?

  1. The property makes the attribute read-only unless a deleter is also defined
  2. The property prevents subclasses from overriding the attribute
  3. The property runs validation code on every assignment and stores the value wherever the setter chooses Correct answer.
  4. The property stores values in the class dictionary rather than the instance dictionary

Explanation

A property intercepts attribute access and delegates to getter and setter functions, allowing validation, transformation or computed storage. It does not change where the underlying value lives, since the setter decides that. Read-only behavior comes from omitting a setter, not from omitting a deleter.

Question 36Advanced OOP

A metaclass defines __call__ to log arguments before delegating to super().__call__. What does this intercept?

  1. Instantiation of classes that use the metaclass Correct answer.
  2. Comparison between two instances of those classes
  3. Definition of new methods on those classes
  4. Attribute access on instances of those classes

Explanation

Calling a class is handled by type.__call__, so overriding __call__ on a metaclass intercepts construction of instances. The override typically logs, then delegates to super().__call__, which invokes __new__ and __init__. Attribute access and comparisons are handled by other hooks.

Question 37Advanced OOP

Which statement about abstract methods declared with abc.abstractmethod is correct?

  1. A subclass must override them before it can be instantiated Correct answer.
  2. They are enforced only when the subclass is itself decorated with ABCMeta
  3. They must be defined in every class that inherits from the abstract base
  4. They can be called directly on the abstract base to obtain a default implementation

Explanation

ABCMeta records unimplemented abstract methods and raises TypeError when instantiation is attempted. A subclass becomes concrete once it overrides all of them. Intermediate abstract subclasses may leave some unimplemented as long as they are not instantiated.

Question 38Advanced OOP

A class defines __bool__ to return a non-boolean value such as an integer. What does Python do when truth-testing an instance?

  1. It ignores __bool__ and falls back to __len__
  2. It raises TypeError because __bool__ must return a bool
  3. It converts the returned value with bool() and uses the result Correct answer.
  4. It uses the value as-is, so any nonzero integer is truthy but a zero integer is falsy

Explanation

The result of __bool__ is passed through bool(), so truthy and falsy values are coerced to a real boolean. Returning an integer therefore works, though returning an actual bool is clearer. If __bool__ is absent, Python consults __len__.

Question 39Advanced OOP

Which statement about calling a base class method explicitly by name, as in Base.method(self), versus using super().method() is correct?

  1. The explicit form bypasses the MRO and always runs that specific base implementation Correct answer.
  2. The explicit form fails when the base class is defined in another module
  3. The explicit form is faster because it skips descriptor lookup entirely
  4. The explicit form and super() are identical in all inheritance graphs

Explanation

Naming the base directly calls that class's function object, skipping further MRO traversal. This can duplicate a call in diamond hierarchies where super() would continue along the chain. The explicit form is sometimes used deliberately to control cooperative behavior.

Question 40Advanced OOP

A class defines __iadd__ to mutate self and return self. What happens when the augmented assignment target is an immutable object of a different class?

  1. Python falls back to __add__ and rebinds the name to the new object Correct answer.
  2. Python calls __iadd__ on the target and raises AttributeError
  3. Python raises TypeError because __iadd__ is defined only on the other class
  4. Python performs an in-place mutation of the immutable object

Explanation

Augmented assignment tries __iadd__ on the left operand; if it is unavailable, Python falls back to __add__ and rebinds the name. Immutable types therefore appear to change through rebinding, not mutation. The original object is untouched.

Question 41Advanced OOP

Which statement correctly describes how __init_subclass__ differs from a metaclass __init__ in controlling subclass creation?

  1. __init_subclass__ is an ordinary classmethod-like hook on the parent and needs no metaclass Correct answer.
  2. __init_subclass__ is invoked once per instance rather than per subclass
  3. __init_subclass__ runs only when the subclass defines no __init__ method
  4. __init_subclass__ receives the subclass namespace before the class body executes

Explanation

__init_subclass__ is implicitly a classmethod on the base and is called after each subclass is created, without requiring a custom metaclass. It receives keyword arguments passed in the class statement. It runs after the class object exists, not before the body executes.

Question 42Advanced OOP

What is the effect of defining __slots__ in a subclass whose base class lacks __slots__?

  1. The subclass raises TypeError at definition time
  2. Instances still get a __dict__ from the base, so slots do not prevent extra attributes Correct answer.
  3. The base class automatically gains the same slots
  4. Instances lose all inherited attributes defined by the base

Explanation

Slot optimization only removes the instance dictionary if every class in the hierarchy omits it. A base without __slots__ already provides __dict__, so subclass instances retain it. Inherited attributes remain available.

Question 43Advanced OOP

Which statement about the __del__ method is accurate for an object participating in a reference cycle?

  1. It is guaranteed to run immediately when the last reference is dropped
  2. It is skipped entirely for objects in cycles because the collector cannot free them
  3. It runs before __init__ if construction fails partway through
  4. Its timing is unpredictable and it may not run before interpreter shutdown Correct answer.

Explanation

Finalizers in cycles depend on the cyclic garbage collector and may be delayed or skipped at shutdown. Code should not rely on __del__ for critical cleanup. Explicit context managers are the recommended alternative.

Question 44Advanced OOP

A class defines __getitem__ but neither __iter__ nor __len__. What happens when a for loop iterates over an instance?

  1. Iteration raises TypeError because __iter__ is missing
  2. Iteration uses __len__ to size the loop and fails with AttributeError
  3. Iteration yields the instance itself once and stops
  4. Iteration uses __getitem__ with increasing integer indices until IndexError is raised Correct answer.

Explanation

The legacy sequence protocol lets a for loop call __getitem__ with 0, 1, 2 and so on until IndexError signals the end. This fallback exists for backward compatibility. Defining __iter__ is preferred for new code.

Question 45Advanced OOP

Which statement about a class that defines __call__ on its instances is correct?

  1. The class itself becomes callable only if its metaclass also defines __call__
  2. Instances become callable and can be used wherever a function accepting arguments is expected Correct answer.
  3. Instances become callable but cannot accept keyword arguments
  4. The class can no longer be instantiated normally

Explanation

Defining __call__ on a class makes its instances callable objects, which is the basis for closures, decorators and stateful callables. Keyword arguments are passed through normally. The class remains instantiable as usual.

Question 46Advanced OOP

A mixin defines a helper method and is listed first in class Service(LoggingMixin, Base). Which behavior results when both mixin and base define the same method name?

  1. The base's version always wins because it is the primary parent
  2. The mixin's version is found first in the MRO and runs unless it delegates with super() Correct answer.
  3. Python raises a conflict error at class definition time
  4. The method is resolved randomly at each call

Explanation

Leftmost bases precede later ones in the MRO, so the mixin's implementation is selected. A cooperative mixin typically calls super() to continue along the chain. Duplicate method names alone do not cause errors.

Question 47Advanced OOP

Which statement about the __class__ cell created by the zero-argument form of super() is correct?

  1. It is created only inside __init__ methods
  2. It refers to the runtime class of the instance at call time
  3. It refers to the class where the method is defined, allowing super() without arguments Correct answer.
  4. It refers to the metaclass of the defining class

Explanation

The compiler supplies a __class__ cell pointing to the lexically enclosing class, which is what zero-argument super() uses. This is why super() fails in functions defined outside a class body. It is available in any method, not just __init__.

Question 48Advanced OOP

What does the register method of an abstract base class accomplish when called with an unrelated class?

  1. It prevents the registered class from being instantiated
  2. It copies the abstract methods into the registered class
  3. It changes the registered class's MRO to include the base
  4. It makes isinstance and issubclass report the registered class as a virtual subclass Correct answer.

Explanation

Virtual subclass registration affects only isinstance and issubclass checks; no methods are inherited and the MRO is unchanged. This supports structural duck typing without inheritance. The registered class remains fully instantiable.

Question 49Advanced OOP

A class defines __eq__ but not __ne__. What happens when the != operator is applied to two instances?

  1. Python uses the negation of __eq__ when __ne__ is not defined Correct answer.
  2. Python returns NotImplemented and then compares hashes
  3. Python falls back to identity comparison with is
  4. Python raises TypeError because __ne__ is required

Explanation

When __ne__ is absent, the default implementation delegates to __eq__ and negates the result, unless __eq__ returns NotImplemented. Defining __eq__ alone therefore gives consistent != behavior. Explicit __ne__ is rarely needed.

Question 50Advanced OOP

Which statement best explains why overriding __getattribute__ requires care when the override accesses self attributes?

  1. The override runs once per class rather than once per access
  2. The override is called only for missing attributes, so recursion is impossible
  3. Any attribute access inside the override recurses unless it bypasses the hook, for example via object.__getattribute__ Correct answer.
  4. The override is ignored unless it also defines __setattr__

Explanation

__getattribute__ is consulted for every attribute access, including those made inside its own body, so naive implementations recurse infinitely. Calling object.__getattribute__(self, name) avoids the loop. Most dynamic behavior is better implemented with __getattr__.