Python抽象基类中的subclasshook方法详解及解析
引言
在 Python 编程中,抽象基类和虚拟子类是重要的概念。Alex 在“水禽和抽象基类”一文中,通过示例展示了即便不注册,抽象基类也能把一个类识别为虚拟子类,而这背后的关键就在于 __subclasshook__ 方法。
抽象基类与虚拟子类的识别
示例说明
文中给出了 Struggle 类的例子:
class Struggle:
def __len__(self): return 23
from collections import abc
isinstance(Struggle(), abc.Sized) # 输出 True
issubclass(Struggle, abc.Sized) # 输出 True
通过 issubclass 和 isinstance 函数确认,Struggle 是 abc.Sized 的子类。这是因为 abc.Sized 实现了 __subclasshook__ 特殊类方法。
__subclasshook__ 方法源码分析
Sized 类的 __subclasshook__ 方法源码如下:
class Sized(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __len__(self):
return 0
@classmethod
def __subclasshook__(cls, C):
if cls is Sized:
if any("__len__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
C.__mro__(即 C 及其超类)中所列的类,如果类的 __dict__ 属性中有名为 __len__ 的属性,就返回 True,表明 C 是 Sized 的虚拟子类。__subclasshook__:白鹅类型中的鸭子类型踪迹
subclasshook 在白鹅类型中添加了鸭子类型的踪迹。我们可以使用抽象基类定义正式接口,通过 isinstance 检查,也可以使用不相关的类,只要实现特定的方法即可(或者做些事情让__subclasshook__信服)。不过,只有提供 __subclasshook__ 方法的抽象基类才能这么做。
自定义抽象基类中实现 __subclasshook__ 的思考
实现的可靠性问题
在 Python 源码中,只有 Sized 这一个抽象基类实现了 __subclasshook__ 方法,且 Sized 只声明了一个特殊方法,只用检查 __len__ 方法。但对于其他特殊方法和基本的抽象基类,很难确定实现特定方法的类就一定符合要求。例如,映射实现了 __len__、__getitem__ 和 __iter__,但不能把它们视作 Sequence 的子类型。
建议
在自己编写的抽象基类中实现 __subclasshook__ 方法可靠性很低。程序员最好让类继承抽象基类,或者使用注册的方式(如 Tombola.register(Spam) )来确保类符合抽象基类的要求。虽然自己实现的 __subclasshook__ 方法还可以检查方法签名和其他特性,但这样做并不值得。
总结
__subclasshook__ 方法为 Python 抽象基类识别虚拟子类提供了便利,但在自定义抽象基类时,要谨慎使用该方法,优先考虑继承和注册的方式来保证代码的可靠性。
作者:钢铁男儿