【问题标题】:python subclasscheck & subclasshookpython子类检查和子类挂钩
【发布时间】:2017-04-07 10:56:18
【问题描述】:

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

【问题讨论】:

    标签: python python-3.x abstract-class metaclass


    【解决方案1】:

    __subclasshook____subclasscheck__ 用于自定义issubclass 函数的行为。 abc source code 中的更多信息。

    __subclasscheck__ 在类的类型(元类)上查找。它不应该为普通类定义。

    __subclasshook__ 检查子类是否被认为是某个 ABC 的子类。这意味着您可以进一步自定义 issubclass 的行为,而无需在您希望将其视为 ABC 子类的每个类上调用 register()。

    这意味着你可以在你的 ABC 类中定义 __subclasshook__ 并带有一些条件,并且所有满足该条件的类都将被视为子类。

    例如:

    from abc import ABCMeta
    
    class Sized(metaclass=ABCMeta):
        @classmethod
        def __subclasshook__(cls, C):
            if cls is Sized:
                if any("__len__" in B.__dict__ for B in C.__mro__):
                    return True
            return NotImplemented
    
    class A(object):
        pass
    
    class B(object):
        def __len__(self):
            return 0
    
    issubclass(A, Sized)  # False
    issubclass(B, Sized)  # True
    

    【讨论】:

    • 为什么需要if cls is Sized: 行?
    【解决方案2】:

    这两种方法都可以用来自定义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__ 的类都将在issubclass 检查中返回True

    >>> 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 的行为,而无需在您想要考虑ABC 的子类的每个类上调用register()。 (这个类方法是从ABC的__subclasscheck__()方法调用的。)

    此方法应返回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__ 而不是使用正常的子类确定。

    例如(来自 CPython collections.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 的子类,它将使用由if cls is Hashable 保护的自定义__subclasshook__ 实现。但是,如果您有一个实际的类实现了Hashable 接口,您不希望它继承__subclasshook__ 机制,但您需要正常的子类机制。

    例如:

    class MyHashable(Hashable):
        def __hash__(self):
            return 10
    
    >>> issubclass(int, MyHashable)
    False
    

    尽管int 实现了__hash__ 并且__subclasshook__ 检查了__hash__ 的实现,但这种情况下的结果是False。它仍然进入Hashable__subclasshook__,但它立即返回NotImplemented,它向ABCMeta 发出信号,表明它应该继续使用正常的实现。如果它没有if cls is Hashable,那么issubclass(int, MyHashable) 将返回True

    什么时候应该使用__subclasscheck__,什么时候应该使用__subclasshook__

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

    您始终可以使用__subclasscheck__,但它必须在元类上实现。

    实际上,如果您实现接口(因为这些接口通常使用abc)并希望自定义子类机制,则使用__subclasshook__。如果你想发明自己的约定,你可以使用__subclasscheck__(就像abc 那样)。因此,在 99.99% 的正常(不好玩)情况下,您只需要 __subclasshook__

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-20
      • 2013-03-06
      • 2019-04-29
      • 2013-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-01
      相关资源
      最近更新 更多