We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Structural Typing and the Hard Parts step 4 of 24
abc.ABC: abstract properties and classmethods, in the right order
@abstractmethod is not a decorator that “marks a method abstract”. It is a
decorator that sets __isabstractmethod__ = True on the object handed to
it, and ABCMeta later collects every such object into
cls.__abstractmethods__. Everything surprising about abstract properties
follows from that one sentence.
Decorator order
The builtin descriptor goes outermost; @abstractmethod goes innermost,
closest to the function:
@property
@abstractmethod
def name(self) -> str: ...
@classmethod
@abstractmethod
def from_config(cls, config: Mapping[str, str]) -> Self: ...
Get it backwards and, on a current CPython, you do not get a subtle bug — you get an immediate
AttributeError: attribute '__isabstractmethod__' of 'property' objects is not writable
at class-creation time, because property and classmethod expose
__isabstractmethod__ as a read-only getset. The starter code has the
order wrong on purpose. Run it once before you fix it.
The genuinely silent case is different, and worth knowing: put any other
wrapping decorator outside @abstractmethod and, unless that wrapper
propagates __isabstractmethod__, the member vanishes from
__abstractmethods__ and the incomplete subclass constructs happily.
abc.update_abstractmethods() (3.10+) exists to recompute the set after a
class decorator has rewritten the body.
abstractproperty, abstractclassmethod and abstractstaticmethod have
been deprecated since Python 3.3. Do not use them.
Your task
Complete an abstract Codec with:
-
an abstract read-only property
name -
an abstract classmethod
from_config(config: Mapping[str, str]) -> Self -
an abstract method
encode(text: str) -> str -
a concrete template method
label(text)returningf"{self.name}:{self.encode(text)}"
and two concrete subclasses, every overriding member carrying @override:
-
RotCodec(shift: int = 1)—nameisf"rot{shift}";encoderotates the string left byshift:text[shift:] + text[:shift].from_configreadsconfig["shift"]as anint, defaulting to1. -
UpperCodec()—nameis"upper";encodeupper-cases.
A third subclass PartialCodec deliberately omits name. Defining it is
perfectly legal; building it is not.
def solve(kind: str, config: dict[str, str], text: str) -> dict[str, object]:
dispatches kind ("rot" / "upper") through a dict[str, type[Codec]],
builds the codec via from_config, and returns:
| key | value |
|---|---|
"label" |
codec.label(text) |
"codec_abstracts" |
sorted(Codec.__abstractmethods__) |
"partial_abstracts" |
sorted(PartialCodec.__abstractmethods__) |
"partial_build" |
try_build(PartialCodec) |
"complete_build" |
try_build(UpperCodec) |
try_build(cls: type) -> str calls cls() and returns "TypeError" if that
raises TypeError, else "constructed".
Why this shape
__abstractmethods__ is the observable artefact of the whole mechanism. If
your decorator order is wrong the class will not even build; if a member is
silently unregistered the set will be missing an entry; and try_build
proves that ABCMeta really does refuse to instantiate — which is exactly
the guarantee a Protocol does not give you (see the protocol
subclassing problem).
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.