Hi all,

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.

Explanation:

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 `@type(X).register`.

Additionally, `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 `enum()`, `cast()` and others.

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 users of 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 `isinstance` checks.

Q: Why not add to the standard library? why not some 3rd party module?
A:
1. Because dynamic usage should be discouraged, similarly to monkey-patching. Note that, like monkey-patching, this has runtime implications which cause "actions from distance".
2. Because it should be used by typeshed and mypy.

The implementation of register() is straightforward:

    def register(*abcs):
        def inner(cls):
            for b in abcs:
                b.register(cls)
            return cls
        return inner


Thanks,
Elazar