小编典典

python subclasscheck和subclasshook

python

方法__subclasscheck____subclasshook__用于确定一个类是否被视为另一个的子类。但是,即使在高级Python书籍中,它们的文档也非常有限。它们的用途是什么?它们之间有什么区别(更高的优先级,他们所指的关系方面等等)?


阅读 450

收藏
2021-01-20

共1个答案

小编典典

两种方法均可用于自定义issubclass()内置函数的结果。

__subclasscheck__

class.__subclasscheck__(self, subclass)

如果子类应被视为类的(直接或间接)子类,则返回true。如果定义,则调用实现issubclass(subclass, class)

请注意,这些方法是在类的类型(元类)上查找的。它们不能定义为实际类中的类方法。这与在实例上调用的特殊方法的查找一致,仅在这种情况下,实例本身是一个类。

此方法是负责issubclass检查定制的特殊方法。就像“注释”中所述,它必须在元类上实现!

class YouWontFindSubclasses(type):
    def __subclasscheck__(cls, subclass):
        print(cls, subclass)
        return False

class MyCls(metaclass=YouWontFindSubclasses):
    pass

class MySubCls(MyCls):
    pass

即使您拥有真正的子类,此实现也会返回False:

>>> issubclass(MySubCls, MyCls)
<class '__main__.MyCls'> <class '__main__.MySubCls'>
False

实际上,__subclasscheck__实现还有更多有趣的用途。例如:

class SpecialSubs(type):
    def __subclasscheck__(cls, subclass):
        required_attrs = getattr(cls, '_required_attrs', [])
        for attr in required_attrs:
            if any(attr in sub.__dict__ for sub in subclass.__mro__):
                continue
            return False
        return True

class MyCls(metaclass=SpecialSubs):
    _required_attrs = ['__len__', '__iter__']

有了这个实现的任何类,定义__len____iter__返回Trueissubclass检查:

>>> issubclass(int, MyCls)  # ints have no __len__ or __iter__
False
>>> issubclass(list, MyCls)  # but lists and dicts have
True
>>> issubclass(dict, MyCls)
True

在这些示例中,我没有调用超类__subclasscheck__,因此禁用了正常issubclass行为(由实现type.__subclasscheck__)。但是重要的是要知道,您还可以选择只是
扩展 正常行为,而不是完全覆盖它:

class Meta(type):
    def __subclasscheck__(cls, subclass):
        """Just modify the behavior for classes that aren't genuine subclasses."""
        if super().__subclasscheck__(subclass):
            return True
        else:
            # Not a normal subclass, implement some customization here.

__subclasshook__

__subclasshook__(subclass)

(必须定义为类方法。)

检查子类是否被视为此ABC的子类。这意味着您可以自定义issubclass进一步的行为,而无需调用register()要考虑为ABC的子类的每个类。(此类方法是从__subclasscheck__()ABC的方法中调用的。)

这个方法应该返回TrueFalseNotImplemented。如果返回True,则将该子类视为此ABC的子类。如果返回False,则即使该子类通常是一个子类,也不会将该子类视为该ABC的子类。如果返回NotImplemented,则使用常规机制继续子类检查。

这里重要的一点是,它是classmethod在类上定义的,并由调用abc.ABC.__subclasscheck__。因此,只有在处理具有ABCMeta元类的类时才可以使用它:

import abc

class MyClsABC(abc.ABC):
    @classmethod
    def __subclasshook__(cls, subclass):
        print('in subclasshook')
        return True

class MyClsNoABC(object):
    @classmethod
    def __subclasshook__(cls, subclass):
        print('in subclasshook')
        return True

这只会进入__subclasshook__第一个:

>>> issubclass(int, MyClsABC)
in subclasshook
True

>>> issubclass(int, MyClsNoABC)
False

请注意,后续的issubclass调用不会再进入__subclasshook__,因为会ABCMeta缓存结果:

>>> issubclass(int, MyClsABC)
True

请注意,通常检查第一个参数是否是类本身。这是为了避免子类“继承”,__subclasshook__而不是使用常规子类确定。

例如(来自CPythoncollections.abc模块):

from abc import ABCMeta, abstractmethod

def _check_methods(C, *methods):
    mro = C.__mro__
    for method in methods:
        for B in mro:
            if method in B.__dict__:
                if B.__dict__[method] is None:
                    return NotImplemented
                break
        else:
            return NotImplemented
    return True

class Hashable(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __hash__(self):
        return 0

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

因此,如果您检查某物是否是其子类,Hashable则将使用由__subclasshook__守护的自定义实现if cls is Hashable。但是,如果您有一个实际的类来实现该Hashable接口,则您不希望它继承该__subclasshook__机制,而是希望使用普通的子类机制。

例如:

class MyHashable(Hashable):
    def __hash__(self):
        return 10

>>> issubclass(int, MyHashable)
False

即使int实现__hash____subclasshook__检查__hash__实现,这种情况下的结果也是False。它仍然输入__subclasshook__of,Hashable但是它立即返回使用正常实现应继续进行处理的NotImplemented信号ABCMeta。如果没有,if cls is Hashableissubclass(int, MyHashable)返回True

当你应该使用__subclasscheck__以及何时__subclasshook__

真的要看
__subclasshook__可以在类上而不是在元类上实现,但要求您使用ABCMeta(或的子类ABCMeta)作为元类,因为该__subclasshook__方法实际上只是Pythonsabc模块引入的约定。

您可以随时使用,__subclasscheck__但必须在元类上实现。

在实践中,__subclasshook__如果要实现接口(因为这些接口通常使用abc)并且要自定义子类机制,则使用。而你使用__subclasscheck__,如果你想创造自己的约定(如abc没有)。因此,在99.99%的正常(不好玩)情况下,您只需要__subclasshook__

2021-01-20