在 Python 中 type 与 object 的关系 这篇文章中,我提到了 Combine Solid Arrows Rule,即:
如果 A 是 B 的子类,且 B 是 C 的子类,那么 A 也是 C 的子类
也就是说子类关系(继承关系)具有传递性:
class C:
pass
class B(C):
pass
class A(B):
pass
issubclass(A, B)
>>> True
issubclass(B, C)
>>> True
issubclass(A, C)
>>> True
A.__mro__
>>> (__main__.A, __main__.B, __main__.C, object)
但我们来看看下面的代码:
from collections import Hashable
issubclass(list, object)
>>> True
issubclass(object, Hashable)
>>> True
issubclass(list, Hashable)
>>> False
很明显,list 是不可哈希的
上面的代码貌似说明了子类关系并不一定具有传递性
但仔细观察会发现一个很不对劲的地方:issubclass(object, Hashable)
的结果为 True
难道 object 是 Hashable 的子类?当然不是了
我们知道在 Python 中 object 已经是”最原始”的父类了,它不继承任何类,它是所有类的父类:
print(object.__base__)
>>> None
object.__mro__
>>> (object,)
难道子类关系不一定具有传递性吗?我们来看看 Hashable 这个类的源码:
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
注意到它的元类并不是 type,而是 ABCMeta,且定义了一个魔法方法 __subclasshook__
__subclasshook__
实际上被元类 ABCMeta 的 __subclasscheck__
调用:
class ABCMeta(type):
def __subclasscheck__(cls, subclass):
"""Override for issubclass(subclass, cls)."""
# Check cache
# 省略部分代码
# Check the subclass hook
ok = cls.__subclasshook__(subclass) # 调用 __subclasshook__
# 省略部分代码
这个方法有点长,这里省去了部分代码
它的注释(Override for issubclass(subclass, cls))告诉我们
__subclasscheck__
其实就是 issubclass()
的魔法方法
当我们调用 issubclass(subclass, cls)
时,如果 cls 定义了 __subclasscheck__
(一般是其元类定义的)
那么 Python 就会调用 __subclasscheck__
而 __subclasscheck__
会去调用 cls 的 __subclasshook__
(一般由cls自己定义)
并把参数 subclass 传给 cls 的 __subclasshook__
这样就可以由 cls 自己来决定 subclass 是否为其子类
而这里的 Hashable 在它的 __subclasshook__
中只是检查 subclass 是否定义了 __hash__
def _check_methods(C, *methods):
# 这里 methods 为 (__hash__,),C 为 __subclasscheck__ 传进来的 subclass
mro = C.__mro__
for method in methods:
for B in mro: # 检查 C 及其所有父类是否含有该方法
if method in B.__dict__:
if B.__dict__[method] is None: # 子类可以通过 __hash__ = None 成为不可哈希的
return NotImplemented
break
else:
return NotImplemented
return True
Hashable 认为只要 subclass 定义了 __hash__
且不为 None,那么它就认为 subclass 是其子类
我个人认为这种检测方式是不合理的,其主要还是因为 Python 中并没有 接口 这种对象
有关 __subclasscheck__
及 Python 中抽象基类的的引入可见 PEP319
class C:
@classmethod
def __subclasshook__(cls, C):
return True
issubclass(object, C)
>>> False
C 并没声明元类,默认就是 type,而 type 并没有定义 __subclasscheck__
因此 __subclasshook__
永远不会被调用
class MyMetaClass(type): # 自定义元类
def __subclasscheck__(cls, subclass): # 直接就返回 True,并没有去调用 __subclasshook__
return True
class C(metaclass=MyMetaClass): # 声明元类为自定义的元类 MyMetaClass
@classmethod
def __subclasshook__(cls, C): # 它只能在其元类的 __subclasscheck__ 中被调用
return False
issubclass(object, C)
>>> True
可以看到真正起决定性的函数是 __subclasscheck__
,而不是 __subclasshook__
所以按照协议,我们应该在 __subclasscheck__
中调用 cls 的 __subclasshook__
,这样就会把决定权交给 cls
这就是为什么一般我们会去使用 Python 给我们写好的 ABCMeta 而不是自定义元类:
from abc import ABCMeta
class C(metaclass=ABCMeta): # 声明元类为 ABCMeta
@classmethod
def __subclasshook__(cls, C):
return True
issubclass(object, C)
>>> True
class C(metaclass=ABCMeta): # 声明元类为 ABCMeta
@classmethod
def __subclasshook__(cls, subclass): # 这里的 cls 就是 C 自己,而 subclass 是待检测的类
if cls in subclass.__mro__:
return True
else:
return False
issubclass(object, C) # 会去调用 C 的 __subclasshook__,object 作为参数 subclass
>>> False
class B(C):
pass
issubclass(B, C)
>>> True
因此,我们可以看到,原本 Python 中的子类关系就是传递的
但自从引入 __subclasscheck__
之后,这种子类关系的决定权就对用户开放了
这使得传递性可以被用户的代码所打破,这种自由在某种程度上来讲是一种缺陷和妥协
参考资料: