方法__subclasscheck__和__subclasshook__用于确定一个类是否被视为另一个的子类。但是,即使在高级Python书籍中,它们的文档也非常有限。它们的用途是什么?它们之间有什么区别(更高的优先级,他们所指的关系方面等等)?
__subclasscheck__
__subclasshook__
两种方法均可用于自定义issubclass()内置函数的结果。
issubclass()
class.__subclasscheck__(self, subclass) 如果子类应被视为类的(直接或间接)子类,则返回true。如果定义,则调用实现issubclass(subclass, class)。 请注意,这些方法是在类的类型(元类)上查找的。它们不能定义为实际类中的类方法。这与在实例上调用的特殊方法的查找一致,仅在这种情况下,实例本身是一个类。
class.__subclasscheck__(self, subclass)
如果子类应被视为类的(直接或间接)子类,则返回true。如果定义,则调用实现issubclass(subclass, class)。
issubclass(subclass, class)
请注意,这些方法是在类的类型(元类)上查找的。它们不能定义为实际类中的类方法。这与在实例上调用的特殊方法的查找一致,仅在这种情况下,实例本身是一个类。
此方法是负责issubclass检查定制的特殊方法。就像“注释”中所述,它必须在元类上实现!
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__返回True的issubclass检查:
__len__
__iter__
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__)。但是重要的是要知道,您还可以选择只是 扩展 正常行为,而不是完全覆盖它:
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__(subclass) (必须定义为类方法。) 检查子类是否被视为此ABC的子类。这意味着您可以自定义issubclass进一步的行为,而无需调用register()要考虑为ABC的子类的每个类。(此类方法是从__subclasscheck__()ABC的方法中调用的。) 这个方法应该返回True,False或NotImplemented。如果返回True,则将该子类视为此ABC的子类。如果返回False,则即使该子类通常是一个子类,也不会将该子类视为该ABC的子类。如果返回NotImplemented,则使用常规机制继续子类检查。
__subclasshook__(subclass)
(必须定义为类方法。)
检查子类是否被视为此ABC的子类。这意味着您可以自定义issubclass进一步的行为,而无需调用register()要考虑为ABC的子类的每个类。(此类方法是从__subclasscheck__()ABC的方法中调用的。)
register()
__subclasscheck__()
这个方法应该返回True,False或NotImplemented。如果返回True,则将该子类视为此ABC的子类。如果返回False,则即使该子类通常是一个子类,也不会将该子类视为该ABC的子类。如果返回NotImplemented,则使用常规机制继续子类检查。
False
NotImplemented
这里重要的一点是,它是classmethod在类上定义的,并由调用abc.ABC.__subclasscheck__。因此,只有在处理具有ABCMeta元类的类时才可以使用它:
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模块):
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则将使用由__subclasshook__守护的自定义实现if cls is Hashable。但是,如果您有一个实际的类来实现该Hashable接口,则您不希望它继承该__subclasshook__机制,而是希望使用普通的子类机制。
Hashable
if cls is Hashable
例如:
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 Hashable则issubclass(int, MyHashable)返回True!
int
__hash__
issubclass(int, MyHashable)
真的要看 __subclasshook__可以在类上而不是在元类上实现,但要求您使用ABCMeta(或的子类ABCMeta)作为元类,因为该__subclasshook__方法实际上只是Pythonsabc模块引入的约定。
abc
您可以随时使用,__subclasscheck__但必须在元类上实现。
在实践中,__subclasshook__如果要实现接口(因为这些接口通常使用abc)并且要自定义子类机制,则使用。而你使用__subclasscheck__,如果你想创造自己的约定(如abc没有)。因此,在99.99%的正常(不好玩)情况下,您只需要__subclasshook__。