[issue23864] issubclass without registration only works for "one-trick pony" collections ABCs.

New submission from Martijn Pieters: The collections.abc documentation implies that *any* of the container ABCs can be used in an issubclass test against a class that implements all abstract methods:
These ABCs allow us to ask classes or instances if they provide particular functionality [...]
In reality this only applies to the "One Trick Ponies" (term from PEP 3119, things like Container and Iterable, those classes with one or two methods). It fails for the compound container ABCs:
from collections.abc import Sequence, Container, Sized class MySequence(object): ... def __contains__(self, item): pass ... def __len__(self): pass ... def __iter__(self): pass ... def __getitem__(self, index): pass ... def __len__(self): pass ... issubclass(MySequence, Container) True issubclass(MySequence, Sized) True issubclass(MySequence, Sequence) False
That's because the One Trick Ponies implement a __subclasshook__ method that is locked to the specific class and returns NotImplemented for subclasses; for instance, the Iterable.__subclasshook__ implementation is: @classmethod def __subclasshook__(cls, C): if cls is Iterable: if any("__iter__" in B.__dict__ for B in C.__mro__): return True return NotImplemented The compound container classes build on top of the One Trick Ponies, so the class test will fail, NotImplemented is returned and the normal ABC tests for base classes that have been explicitly registered continues, but this won't include unregistered complete implementations. Either the compound classes need their own __subclasshook__ implementations, or the documentation needs to be updated to make it clear that without explicit registrations the issubclass() (and isinstance()) tests only apply to the One Trick Ponies. ---------- assignee: docs@python components: Documentation, Library (Lib) messages: 240060 nosy: docs@python, mjpieters priority: normal severity: normal status: open title: issubclass without registration only works for "one-trick pony" collections ABCs. _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Changes by Jon Clements <joncle@googlemail.com>: ---------- nosy: +joncle _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Martijn Pieters added the comment: I should have added the mixin methods for the Sequence implementation; the more complete demonstration is:
from collections.abc import Sequence, Container, Sized class MySequence(object): ... def __contains__(self, item): pass ... def __len__(self): pass ... def __iter__(self): pass ... def __getitem__(self, index): pass ... def __len__(self): pass ... def __reversed__(self): pass ... def index(self, item): pass ... def count(self, item): pass ... issubclass(MySequence, Container) True issubclass(MySequence, Sized) True issubclass(MySequence, Sequence) False
---------- _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Antti Haapala added the comment: This does apply to all versions of Python from 2.6 up. Registering does work of course. I believe the reason for not having the __subclasshook__ is the following sentence in PEP 3119: "ABCs are intended to solve problems that don't have a good solution at all in Python 2, such as distinguishing between mappings and sequences." This used to be worse in <3.3 because there if you ever inherit from `Sequence` you will always end up having `__dict__`, even if you just want `__slots__`. (By the way, if Py2 documentation is fixed, it should also say that these ABCs are new as of 2.6, not since 2.4 like the rest of the collections module). ---------- nosy: +ztane _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

eryksun added the comment: Probably I'm overlooking something, but why isn't this hook defined cooperatively, with a terminating base class method that returns True? If the call chain progresses to the base, then all of the interfaces have been satisfied. Otherwise one of the bases returns NotImplemented. If it's implemented cooperatively, then the `cls is Iterable` check can be removed, because it returns super().__subclasshook__(C) instead of True. ---------- nosy: +eryksun _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Saul Shanabrook added the comment: I have added a failing test to based on the first example, of a class that provides the necessary methods, but fails to be an instance of Sequence. ---------- keywords: +patch nosy: +saulshanabrook Added file: http://bugs.python.org/file38935/patch.diff _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Changes by Saul Shanabrook <s.shanabrook@gmail.com>: Removed file: http://bugs.python.org/file38935/patch.diff _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Changes by Saul Shanabrook <s.shanabrook@gmail.com>: Added file: http://bugs.python.org/file38937/patch.diff _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Changes by Saul Shanabrook <s.shanabrook@gmail.com>: Removed file: http://bugs.python.org/file38937/patch.diff _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Changes by Saul Shanabrook <s.shanabrook@gmail.com>: Added file: http://bugs.python.org/file38938/patch.diff _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue23864> _______________________________________

Géry <gery.ogam@gmail.com> added the comment: Guido, could we add those missing __subclasshook__ for consistency? ---------- nosy: +gvanrossum, maggyero _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue23864> _______________________________________

Guido van Rossum <guido@python.org> added the comment: No, I consider this is a documentation problem. I don't recall why the docs say that (I don't even know if they still say that or whether Martijn misread them), but IMO this should not be changed. ---------- _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue23864> _______________________________________

Cheryl Sabella <cheryl.sabella@gmail.com> added the comment: This isn't meant as a comment from any previous posts. It's simply meant to correct a statement (based on new information in the past 2 years) from the original post. Since this original report, some ABCs that are not "One Trick Ponies" have been added which implement __subclasshook__. `Collection` is one of those, so using the original example:
from collections.abc import Sequence, Container, Sized, Collection class MySequence(object): ... def __contains__(self, item): pass ... def __len__(self): pass ... def __iter__(self): pass ... def __getitem__(self, index): pass ... def __len__(self): pass ... def __reversed__(self): pass ... def index(self, item): pass ... def count(self, item): pass ... issubclass(MySequence, Container) True issubclass(MySequence, Sized) True issubclass(MySequence, Sequence) False issubclass(MySequence, Collection) True
Collection is not a "One Trick Pony" because it is used for Sized, Iterable Containers. Generator, Coroutine, and ASyncGenerator are also not "One Trick Ponies" (although they are defined under that section in _collections_abc.py). Again, for reference, the definition of One Trick Pony from PEP3119 is: These abstract classes represent single methods like __iter__ or __len__. If only One Trick Ponies implemented __subclasshook__, then the original documentation issue:
These ABCs allow us to ask classes or instances if they provide particular functionality, for example:
maybe could have been changed to:
These ABCs allow us to ask classes or instances if they provide singular functionality, for example:
But, that's not really correct anymore. ---------- nosy: +cheryl.sabella _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue23864> _______________________________________

Change by Manuel Cerón <ceronman@gmail.com>: ---------- nosy: +ceronman _______________________________________ Python tracker <report@bugs.python.org> <https://bugs.python.org/issue23864> _______________________________________
participants (9)
-
Antti Haapala
-
Cheryl Sabella
-
eryksun
-
Guido van Rossum
-
Géry
-
Jon Clements
-
Manuel Cerón
-
Martijn Pieters
-
Saul Shanabrook