问题描述
方法__subclasscheck__
和__subclasshook__
用于确定某个类是否被视为另一个类的子类.但是,即使在高级Python书籍中,它们的文档也非常有限.
The methods __subclasscheck__
and __subclasshook__
are used to determine if a class is regarded as subclass of another. However, their documentation is very limited, even in advanced Python books. How are they meant to be used and what is their difference (higher priority, side of relationship they refer to etc...)?
推荐答案
两种方法都可以用于自定义 issubclass()
内置函数.
Both methods can be used to customize the result of the issubclass()
built-in function.
如果子类应被视为类的(直接或间接)子类,则返回true.如果定义,则调用以实现issubclass(subclass, class)
.
Return true if subclass should be considered a (direct or indirect) subclass of class. If defined, called to implement issubclass(subclass, class)
.
请注意,这些方法是在类的类型(元类)上查找的.它们不能在实际的类中定义为类方法.这与在实例上调用的特殊方法的查找一致,仅在这种情况下,实例本身是一个类.
Note that these methods are looked up on the type (metaclass) of a class. They cannot be defined as class methods in the actual class. This is consistent with the lookup of special methods that are called on instances, only in this case the instance is itself a class.
此方法是负责自定义issubclass
检查的特殊方法.就像注"声明必须在元类上实现!
This method is the special method that is responsible for the customization of the issubclass
check. Like the "Note" states it has to implemented on the metaclass!
class YouWontFindSubclasses(type):
def __subclasscheck__(cls, subclass):
print(cls, subclass)
return False
class MyCls(metaclass=YouWontFindSubclasses):
pass
class MySubCls(MyCls):
pass
即使您拥有真正的子类,此实现也会返回False:
This implementation will return False even if you have genuine subclasses:
>>> issubclass(MySubCls, MyCls)
<class '__main__.MyCls'> <class '__main__.MySubCls'>
False
__subclasscheck__
实现实际上有更有趣的用途.例如:
There are actually more interesting uses for __subclasscheck__
implementations. For example:
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
:
With this implementation any class that defines __len__
and __iter__
will return True
in a issubclass
check:
>>> 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__
实现).但重要的是要知道,您也可以选择扩展正常行为,而不是完全覆盖它:
In these examples I haven't called the superclasses __subclasscheck__
and thereby disabled the normal issubclass
behavior (which is implemented by type.__subclasscheck__
). But it's important to know that you can also choose to just extend the normal behavior instead of completely overriding it:
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__
(必须定义为类方法.)
(Must be defined as a class method.)
检查子类是否被视为此ABC的子类.这意味着您可以进一步自定义issubclass
的行为,而无需在要考虑ABC的子类的每个类上调用register()
. (此类方法是从ABC的__subclasscheck__()
方法调用的.)
Check whether subclass is considered a subclass of this ABC. This means that you can customize the behavior of issubclass
further without the need to call register()
on every class you want to consider a subclass of the ABC. (This class method is called from the __subclasscheck__()
method of the ABC.)
此方法应返回True
,False
或NotImplemented
.如果返回True
,则该子类被视为此ABC的子类.如果它返回False
,则即使该子类通常是一个子类,也不会将该子类视为该ABC的子类.如果返回NotImplemented
,则使用常规机制继续子类检查.
This method should return True
, False
or NotImplemented
. If it returns True
, the subclass is considered a subclass of this ABC. If it returns False
, the subclass is not considered a subclass of this ABC, even if it would normally be one. If it returns NotImplemented
, the subclass check is continued with the usual mechanism.
这里重要的一点是,它在类上定义为classmethod
,并由abc.ABC.__subclasscheck__
调用.因此,只有在处理具有ABCMeta
元类的类时,才可以使用它:
The important bit here is that it's defined as classmethod
on the class and it's called by abc.ABC.__subclasscheck__
. So you can only use it if you're dealing with classes that have an ABCMeta
metaclass:
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
会缓存结果:
Note that subsequent issubclass
calls don't go into the __subclasshook__
anymore because ABCMeta
caches the result:
>>> issubclass(int, MyClsABC)
True
请注意,通常检查第一个参数是否是类本身.这是为了避免子类继承". __subclasshook__
而不是使用常规的子类确定.
Note that you generally check if the first argument is the class itself. That's to avoid that subclasses "inherit" the __subclasshook__
instead of using normal subclass-determination.
例如(来自CPython collections.abc
模块):
For example (from the CPython collections.abc
module):
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__
机制,而是希望使用普通的子类机制.
So if you check if something is a subclass of Hashable
it will use the custom __subclasshook__
implementation that is guarded by the if cls is Hashable
. However if you have an actual class implementing the Hashable
interface you don't want it to inherit the __subclasshook__
mechanism but you want the normal subclass mechanism.
例如:
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
!
Even though int
implements __hash__
and the __subclasshook__
checks for an __hash__
implementation the result in this case is False
. It still enters the __subclasshook__
of Hashable
but it immediately returns NotImplemented
which signals to ABCMeta
that it should proceed using the normal implementation. If it didn't have the if cls is Hashable
then issubclass(int, MyHashable)
would return True
!
这真的取决于. __subclasshook__
可以在类而不是元类上实现,但要求您使用ABCMeta
(或ABCMeta
的子类)作为元类,因为__subclasshook__
方法实际上只是Pythons abc
引入的约定.模块.
It really depends. __subclasshook__
can be implemented on the class instead of the metaclass, but requires that you use ABCMeta
(or a subclass of ABCMeta
) as metaclass because the __subclasshook__
method is actually just a convention introduced by Pythons abc
module.
您始终可以使用__subclasscheck__
,但必须在元类上实现.
You can always use __subclasscheck__
but it has to be implemented on the metaclass.
在实践中,如果实现接口(因为这些接口通常使用abc
)并且要自定义子类机制,则可以使用__subclasshook__
.如果要发明自己的约定(如abc
那样),则可以使用__subclasscheck__
.因此,在99.99%的正常(不好玩)的情况下,您只需要__subclasshook__
.
In practice you use __subclasshook__
if you implement interfaces (because these normally use abc
) and want to customize the subclass mechanism. And you use __subclasscheck__
if you want to invent your own conventions (like abc
did). So in 99.99% of the normal (not fun) cases you only need __subclasshook__
.
这篇关于python子类检查&子类挂钩的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!