A developer is designing a class hierarchy for a library system. They want to ensure that a method 'borrow' in the base class 'Item' can be overridden by subclasses like 'Book' and 'DVD', but the base implementation should not be callable directly. Which approach best achieves this?
Correct: defining borrow to raise NotImplementedError creates an explicit contract in which the base class provides no usable behavior. If a concrete subclass forgets to override it, calling that method fails loudly at runtime instead of silently returning a meaningless value. Subclasses that override borrow participate in normal polymorphic dispatch, so callers can treat all library materials uniformly through a base-class reference. This is the classic Python idiom for a semi-abstract method when the abc module is not used.
Why this answer
Raising `NotImplementedError` in the base class `Item.borrow` makes the method abstract in practice: it cannot be called directly without causing an error, forcing subclasses like `Book` and `DVD` to provide their own override. This pattern enforces that the base implementation is never invoked accidentally, while still allowing polymorphic dispatch through inheritance.
Exam trap
Python Institute often tests the distinction between preventing base class instantiation versus preventing base method invocation — candidates mistakenly think `pass` or a static method achieves the same effect, but only raising `NotImplementedError` ensures the base method cannot be called directly.
How to eliminate wrong answers
Option B is wrong because defining `borrow` as a static method prevents it from receiving the instance (`self`) or class (`cls`) reference, making it unsuitable for polymorphic override in a class hierarchy where instance-specific behavior is needed. Option C is wrong because a class method receives the class as the first argument, not the instance, which breaks the typical override pattern for instance methods like `borrow` that depend on per-object state (e.g., a specific book's availability). Option D is wrong because defining `borrow` with `pass` provides a silent no-op default that can be called directly without error, failing the requirement that the base implementation should not be callable directly.