实现 __contains__ 方法时从 collections.abc.Container class 隐式继承

Implicit inheritance from the collections.abc.Container class when implementing __contains__ method

所以,我对 Python 有点陌生,对 OOP 范式也有点陌生,最近我遇到了一些似乎有点违反直觉的事情。例如,查看下面的代码(运行 on Python 3.9):

from collections.abc import Container
class OddContainer:
    
    def __contains__(self, x):
        if not isinstance(x, int) or not x%2:
            return False
        return True
            
if __name__ == '__main__':
    
    x = OddContainer()
    print(isinstance(x, Container))

这里的print会输出True,说明OddContainerContainer的subclass,但是OddContainerclass 没有明确指定它的超classes。 有人能告诉我这里到底发生了什么吗?说任何在内部重载 __contains__() 方法的 class 成为 Container class 的子 class 是否正确,或者这是一个天真的解释?如果前者是正确的,那么还有其他情况会发生这种通过方法重载的继承吗?

提前致谢。

来自docs of isinstance

Return True if the object argument is an instance of the classinfo argument, or of a (direct, indirect or virtual) subclass thereof.

强调我的。当跟随“virtual”的link时,它会导致词汇表中对“ABC”的描述指出:

ABCs introduce virtual subclasses, which are classes that don’t inherit from a class but are still recognized by isinstance() and issubclass(); see the abc module documentation.

因此,OddContainer 被认为是“虚拟子类”,因为它满足 Container ABC。

TL;DR isinstance 并非完全基于继承。被测试的 class 可以为自己定义成为 class 实例的含义。 (不要混淆“x 是 C 的一个实例”和“x 的类型继承自 C”。)


Container 定义 __subclasshook__ 使任何具有 __contains__ 方法的 class 被视为子 class,无论是否 Container 是 class 通过继承的实际祖先。

这是 collections.abc.Container](https://github.com/python/cpython/blob/3.9/Lib/_collections_abc.py#L388) 在 Python 3.9 中的完整定义:

class Container(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            return _check_methods(C, "__contains__")
        return NotImplemented

    __class_getitem__ = classmethod(GenericAlias)

__subclasshook__ 优先于继承检查。

当判断某些 class C 是否是 Container 的子 class 时,调用 Container.__subclasshook__(C)。如果C.__contains__存在,则returnsTrue;如果不是,则 returns 错误。 (我不确定是否值得深入研究如何调用 Container.__subclasshook__cls 不是 Container。)