python subclasscheck & subclasshook
Solution 1:
Both methods can be used to customize the result of the issubclass()
built-in function.
__subclasscheck__
class.__subclasscheck__(self, subclass)
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.
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
This implementation will return False even if you have genuine subclasses:
>>> issubclass(MySubCls, MyCls)
<class '__main__.MyCls'> <class '__main__.MySubCls'>
False
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__']
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
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__
__subclasshook__(subclass)
(Must be defined as a class method.)
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 callregister()
on every class you want to consider a subclass of the ABC. (This class method is called from the__subclasscheck__()
method of the ABC.)This method should return
True
,False
orNotImplemented
. If it returnsTrue
, the subclass is considered a subclass of this ABC. If it returnsFalse
, the subclass is not considered a subclass of this ABC, even if it would normally be one. If it returnsNotImplemented
, the subclass check is continued with the usual mechanism.
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
This will only go into the __subclasshook__
of the first:
>>> issubclass(int, MyClsABC)
in subclasshook
True
>>> issubclass(int, MyClsNoABC)
False
Note that subsequent issubclass
calls don't go into the __subclasshook__
anymore because ABCMeta
caches the result:
>>> issubclass(int, MyClsABC)
True
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.
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
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.
For example:
class MyHashable(Hashable):
def __hash__(self):
return 10
>>> issubclass(int, MyHashable)
False
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
!
When should you use __subclasscheck__
and when __subclasshook__
?
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.
You can always use __subclasscheck__
but it has to be implemented on the metaclass.
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__
.
Solution 2:
__subclasshook__
and __subclasscheck__
are used to customize the behavior of issubclass
function.
A lot more information in abc source code.
__subclasscheck__
is looked up on the type (metaclass) of a class. It shouldn't be defined for an ordinary class.
__subclasshook__
check whether subclass is considered a subclass of some 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.
Which means you can define __subclasshook__
in your ABC class with some condition and all classes that satisfy that condition will be considerate as a subclass.
For example:
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