__subclasscheck__
和__subclasshook__
方法用于确定一个类是否被视为另一个类的子类。但是,它们的文档非常有限,即使是在高级Python书籍中也是如此。它们应该如何使用,它们的区别是什么(更高的优先级、它们所指的关系的方向等)?
__subclasscheck__
和__subclasshook__
方法用于确定一个类是否被视为另一个类的子类。但是,它们的文档非常有限,即使是在高级Python书籍中也是如此。它们应该如何使用,它们的区别是什么(更高的优先级、它们所指的关系的方向等)?
这两种方法都可以用于定制内置函数issubclass()
的结果。
__subclasscheck__
class.__subclasscheck__(self, subclass)
如果子类应该被视为(class)的(直接或间接)子类,则返回True。如果定义了,则调用以实现
issubclass(subclass, class)
。请注意,这些方法在类的类型(元类)上查找。它们不能在实际类中定义为类方法。这与在实例上调用的特殊方法的查找方式一致,只是在这种情况下,实例本身就是一个类。
这个方法是负责定制issubclass
检查的特殊方法。正如“Note”所述,它必须在元类上实现!
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__()
方法中调用。)此方法应返回
True
、False
或NotImplemented
。如果它返回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
,这表明应该使用正常实现进行处理。如果没有 if cls is Hashable
,那么 issubclass(int, MyHashable)
将返回 True
!
__subclasscheck__
和 __subclasshook__
?这真的取决于具体情况。可以在类上实现 __subclasshook__
而不是元类,但需要使用 ABCMeta
(或 ABCMeta
的子类)作为元类,因为 __subclasshook__
方法实际上只是 Python 的 abc
模块引入的约定。
您始终可以使用 __subclasscheck__
,但必须在元类上实现它。
实际上,如果要实现接口(因为这些通常使用 abc
),并且想要自定义子类机制,则使用 __subclasshook__
。如果想要发明自己的约定(例如 abc
所做的),则使用 __subclasscheck__
。因此,在正常情况下(不是有趣的情况下),99.99% 的情况下,您只需要 __subclasshook__
。
__subclasshook__
和 __subclasscheck__
用于定制 issubclass
函数的行为。更多信息请参考 abc 源代码。
__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:
这一行代码? - vikkyhacksSized
并继承 __subclasshook__
时,并不会将所有具有 __len__
方法的类都视为该子类的子类。 - timgeb
if cls is Hashable
,则issubclass(int,MyHashable)
将返回True
!”那有什么问题呢?我们正在执行结构子类型检查,即检查int
的接口是否包括MyHashable
的接口(尽管_check_methods
仅检查方法的存在,忽略了它们的签名,这使得检查不完整,原因我不明白,但那是另一个话题),所以我们实际上期望issubclass(int,MyHashable)
是True
。 - Géry Ogamissubclass(int, MyHashable)
是True
。但由于这不会发生,非协议类应使用命名子类型,因此我同意结构子类型应限制在具有if cls is Hashable
保护的协议类中。再次感谢。 - Géry Ogamissubclass(int, MyHashable) = true
可以得出issubclass(int, Hashabel) = true
(但不一定反过来成立)?但是,这确实有点难以理解(即使对我来说也是如此——请记住,我是4年前写的答案 :D),所以感谢你提出这个问题 :) - MSeifertissubclass(int, MyHashable) is True
和issubclass(MyHashable, Hashable) is True
意味着issubclass(int, Hashable) is True
。不客气 :) - Géry Ogamissubclass(MyHashable, Hashable) is True
和issubclass(int, Hashable) is True
,这意味着issubclass(int, MyHashable) is True
。 - MSeifert