tl;dr: I propose adding a
register() decorator, to be used like this:
@abc.register(Abc1, Abc2) class D: ...
For preexisting classes I propose adding a magic static variable
__registered__, to be handled by ABCMeta:
class Abc1(metaclass=ABCMeta): __registered__ = [D]
Both forms has the effects of calling ABCMeta.register with the corresponding parameters.
It is possible to register an abstract base class B for preexisting classes A using B.register(A). This form is very flexible - probably too flexible, since the operation changes the type of A, and doing it dynamically makes it difficult for type checker (and human beings) to analyze.
But what about new classes? Registering a new class is possible using B.register as a decorator, since it already its argument:
@Iterable.register @Sequence.register class A: pass
However, due to restrictions on the expressions allowed inside a decorator, this would not work with generic ABCs, which are common and heavily used in type-checked projects:
>>> @Iterable[str].register File "<stdin>", line 1 @Iterable[str].register ^ SyntaxError: invalid syntax
While it might be possible to infer the argument, it is not always the
case, and adds additional unnecessary burden to type-checker and to the
reader; discouraging its use. This kind of restrictions may also prevent
some other conceivable forms, such as
abc.register() is also easier to analyze as a "syntactic
construct" from the point of view the type checker, similar to the way
checkers handle some other constructs such as
Finally, there's uninformative repetition of
register for multiple ABCs,
and waste of vertical screen space.
Q: Why not subclass B? A: Since it forces the metaclass of A to be (a subclass of) ABCMeta, which can cause inconsistency in metaclass structure for derived classes. This issue can surface when using Enum (whose metaclass is EnumMeta):
>>> class A(Iterable, Enum): pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: metaclass conflict: the metaclass of a derived class must be
a (non-strict) subclass of the metaclasses of all its bases
This causes problems in typeshed, since it uses inheritance to register
str as a
Sequence[str], for example. As a result, it affects
mypy trying to define
class A(str, Enum): ...
Q: Why not add to the typing module?
A: Because registering an abstract base class has runtime meaning, such as
the result of
Q: Why not add to the standard library? why not some 3rd party module? A:
The implementation of register() is straightforward:
def register(*abcs): def inner(cls): for b in abcs: b.register(cls) return cls return inner