虽然你可以做任何你想做的事情,通常在__subclasshook__()方法中应该遵循通用 模式。标准的过程是检查在给定类的 MRO 中的某处定义的方法的集合是否可用,如下所示:
from abc import ABCMeta, abstractmethod
class Pushable(metaclass=ABCMeta):
@abstractmethod
def push(self, x): """ 推入任意参数
"""
@classmethod
def __subclasshook__(cls, C):
if cls is Pushable:
if any("push" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
通过以这种方式定义的__subclasshook__()方法,你现在可以确认隐式实现接口的 实例也被视为接口的实例:
>>> class SomethingWithPush:
... def push(self, x):
... pass
...
>>> isinstance(SomethingWithPush(), Pushable)
True
不幸的是,这种验证类型兼容性和实现完整性的方法没有考虑类方法的签名。因此, 如果预期参数的数量与实现的不同,仍将被认为是兼容的。在大多数情况下,这不是一个 问题,但是如果你需要这样细粒度的控制接口,zope.interface 包可以做到。如前所 述,__subclasshook__()方法不会限制你在 isinstance()函数的逻辑中添加更多复 杂性以实现类似的控制级别。
补充抽象基类的另外两个功能是函数注解和类型提示。函数注解是在第 2 章中简要描述的 语法元素。它允许你用任意表达式注解函数及其参数。如第 2 章中所述,在类级别之下,这只是 一个不提供任何语法意义的功能桩。在使用此功能的标准库中没有实用程序来强制执行任何行 为。无论如何,你可以使用它作为一个方便且轻量级的方式通知开发人员期望的参数接口。例如, IRectangle 接口之前是使用 zope.interface,现在考虑使用抽象基类重写它,如下所示:
from abc import (
ABCMeta,
abstractmethod,
abstractproperty
)
class IRectangle(metaclass=ABCMeta):
@abstractproperty
def width(self):
return
@abstractproperty
def height(self):
return
@abstractmethod
def area(self):
""" 返回矩形的面积 """
@abstractmethod
def perimeter(self):
""" 返回矩形的周长 """
@classmethod
def __subclasshook__(cls, C):
if cls is IRectangle:
if all([
any("area" in B.__dict__ for B in C.__mro__), any("perimeter" in B.__dict__ for B in C.__mro__), any("width" in B.__dict__ for B in C.__mro__), any("height" in B.__dict__ for B in C.__mro__),
]):
return True
return NotImplemented
如果你有一个只在矩形上工作的函数,例如说 draw_rectangle(),你可以使用如
下所示的方式对期望参数的接口进行注解:
def draw_rectangle(rectangle: IRectange):
...
这只给开发人员增加了一些有关预期的信息。甚至这是通过非正式约定,因为,我们 知道,纯注解不包含句法意义。但是,它们在运行时是可访问的,因此我们可以做更多的 事情。这里是一个通用装饰器的示例实现,它能够验证函数注解的接口,如果它使用抽象 基类提供的话,如下所示:
def ensure_interface(function):
signature = inspect.signature(function) parameters = signature.parameters
@wraps(function)
def wrapped(*args, **kwargs):
bound = signature.bind(*args, **kwargs)
for name, value in bound.arguments.items():
annotation = parameters[name].annotation
if not isinstance(annotation, ABCMeta):
continue
if not isinstance(value, annotation):
raise TypeError(
"{} does not implement {} interface"
"".format(value, annotation)
)
function(*args, **kwargs)
return wrapped
一旦完成,我们可以创建一些具体的类,它隐式实现 IRectangle 接口(不继承 IRectangle),并更新 draw_rectangle()函数的实现,看看整个解决方案是如何工 作的,如下所示:
class ImplicitRectangle:
def __init__(self, width, height):
self._width = width self._height = height
@property
def width(self):
return self._width
@property
def height(self):
return self._height def area(self):
return self.width * self.height
def perimeter(self):
return self.width * 2 + self.height * 2
@ensure_interface
def draw_rectangle(rectangle: IRectangle):
print(
"{} x {} rectangle drawing"
"".format(rectangle.width, rectangle.height)
)