【发布时间】:2017-04-07 10:56:18
【问题描述】:
__subclasscheck__ 和__subclasshook__ 方法用于确定一个类是否被视为另一个类的子类。但是,它们的文档非常有限,即使在高级 Python 书籍中也是如此。它们的用途是什么,它们有什么区别(更高的优先级、它们所指的关系方面等...)?
【问题讨论】:
标签: python python-3.x abstract-class metaclass
__subclasscheck__ 和__subclasshook__ 方法用于确定一个类是否被视为另一个类的子类。但是,它们的文档非常有限,即使在高级 Python 书籍中也是如此。它们的用途是什么,它们有什么区别(更高的优先级、它们所指的关系方面等...)?
【问题讨论】:
标签: python python-3.x abstract-class metaclass
__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: 行?
这两种方法都可以用来自定义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__()方法调用的。)此方法应返回
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,它向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__。
【讨论】: