Fix generics inheritance and drop __subclasscheck__ by bintoro · Pull Request #207 · python/typing
I don't expect this to be reviewed anytime soon, but the effort is now fairly complete, so might as well put it out there for comments.
Resolves #136
Resolves #202
Resolves #203
At the heart of #136 is a conflation between type compatibility and subtype relationships. Even though Sequence[Manager] is compatible with Sequence[Employee], it's not a subclass of it. Rather, both derive from Sequence[+T_co].
To address this, the PR introduces a new helper is_compatible(), which implements the "is consistent with or a subtype of" relation. In short, it does what issubclass() has been doing until now.
The practical end result is that
-
issubclass()is no longer applicable to the special typesAny,Union, etc. -
is_compatible()always works — if neither argument is a special type,is_compatible(T1, T2)reduces toissubclass(T1, T2) -
issubclass()acquires its normal semantics w.r.t. generics:>>> issubclass(Sequence[Manager], Sequence[Employee]) False >>> issubclass(Sequence[Manager], Sequence) True
whereas:
>>> is_compatible(Sequence[Manager], Sequence[Employee]) True
Commit summary
There are three commits:
- The first simply stops the
__extra__linkage from leaking down to generics' subclasses. This fixes the bug whereissubclass(dict, CustomMapping) == True. [Diff] - The second adds the
__extra__ABCs as actual base classes of theirtypingtwins. It also refactorsGenericMeta.__subclasscheck__into a compatibility check and a__subclasshook__dedicated to handling the__extra__connection. [Diff] This achieves a few things:- User-defined generics become recognized as subclasses of the associated
collectionsABCs. - Abstract and mixin methods from
collections.abcbecome available. issubclass(list, Iterable[X])no longer returns true for arbitraryX(Kill __subclasscheck__ #136).
- User-defined generics become recognized as subclasses of the associated
- The final commit is largely a mechanical change: the special types are made to raise a
TypeErrorin response toissubclass(). Every type metaclass gets a method named__is_compatible__to host the prior__subclasscheck__logic. [Diff]
Notes
-
Full parity is preserved in using unparameterized types in place of the corresponding
collections.abcclasses inisinstance()andissubclass()calls:issubclass(type, typing.Callable) == issubclass(type, collections.Callable) == True issubclass(list, typing.Iterable) == issubclass(list, collections.Iterable) == True ...
-
The intent has been not to mess with the types' behavior. That said, the introduction of
is_compatible()yields a couple of automatic improvements regardingAny:- The relation evaluates to true when either argument is
Any; previouslyissubclass(Any, C)would return false for some arbitrary classCbut true for atypingtype. is_compatible(Iterable[Any], Iterable[int])correctly returns true.
- The relation evaluates to true when either argument is
-
The merging of Specific asserts #205 caused a huge conflict in the tests. I will look into it eventually if there's interest in merging this.
-
The proposal is compatible with the plan to convert some of the collection types to protocols, if that's still on the table.