zope.interface 库是克服 Python 接口设计中歧义性的一种方法。让我们来看一下它。
隐式接口不符合禅意
Python 之禅 足够宽松,并且自相矛盾,以至于你可以从中证明任何事情。让我们思考一下它最著名的原则之一:“显式优于隐式。”
传统上,Python 中一直是隐式的一件事是预期的接口。函数文档中说明期望一个“类文件对象”或一个“序列”。但是什么是类文件对象?它支持 .writelines 吗?.seek 呢?什么是“序列”?它支持步进切片吗,例如 a[1:10:2]?
最初,Python 的答案是所谓的“鸭子类型”,取自短语“如果它走起来像鸭子,叫起来像鸭子,那它可能就是鸭子。” 换句话说,“尝试一下看看”,这可能是你能得到的最隐式的方式。
为了使这些事情变得显式,你需要一种表达预期接口的方法。最早用 Python 编写的大型系统之一是 Zope Web 框架,它迫切需要这些东西来明确渲染代码(例如)对“类用户对象”的期望。
引入 zope.interface,它由 Zope 开发,但作为单独的 Python 包发布。Zope.interface 帮助声明接口的存在、哪些对象提供这些接口以及如何查询这些信息。
想象一下编写一个简单的 2D 游戏,它需要各种东西来支持“精灵”接口;例如,指示边界框,还要指示对象何时与框相交。与其他一些语言不同,在 Python 中,属性访问作为公共接口的一部分是一种常见的做法,而不是实现 getter 和 setter。边界框应该是一个属性,而不是一个方法。
渲染精灵列表的方法可能如下所示
def render_sprites(render_surface, sprites):
"""
sprites should be a list of objects complying with the Sprite interface:
* An attribute "bounding_box", containing the bounding box.
* A method called "intersects", that accepts a box and returns
True or False
"""
pass # some code that would actually render
游戏将有许多处理精灵的函数。在每个函数中,你都必须在文档字符串中指定预期的契约。
此外,某些函数可能期望更复杂的精灵对象,也许是具有 Z 顺序的对象。我们将不得不跟踪哪些方法期望 Sprite 对象,哪些方法期望 SpriteWithZ 对象。
如果能够明确和清楚地说明精灵是什么,以便方法可以声明“我需要一个精灵”并严格定义该接口,那不是很好吗?引入 zope.interface。
from zope import interface
class ISprite(interface.Interface):
bounding_box = interface.Attribute(
"The bounding box"
)
def intersects(box):
"Does this intersect with a box"
这段代码乍一看有点奇怪。这些方法不包含 self(这是一种常见的做法),并且它有一个 Attribute 的东西。这是在 zope.interface 中声明接口的方式。它看起来很奇怪,因为大多数人不习惯严格声明接口。
这种做法的原因是接口显示了方法的调用方式,而不是它的定义方式。因为接口不是超类,所以它们可以用于声明数据属性。
接口的一种可能的实现可以是圆形精灵
@implementer(ISprite)
@attr.s(auto_attribs=True)
class CircleSprite:
x: float
y: float
radius: float
@property
def bounding_box(self):
return (
self.x - self.radius,
self.y - self.radius,
self.x + self.radius,
self.y + self.radius,
)
def intersects(self, box):
# A box intersects a circle if and only if
# at least one corner is inside the circle.
top_left, bottom_right = box[:2], box[2:]
for choose_x_from (top_left, bottom_right):
for choose_y_from (top_left, bottom_right):
x = choose_x_from[0]
y = choose_y_from[1]
if (((x - self.x) ** 2 + (y - self.y) ** 2) <=
self.radius ** 2):
return True
return False
这显式声明了 CircleSprite 类实现了该接口。它甚至使我们能够验证该类是否正确实现了它
from zope.interface import verify
def test_implementation():
sprite = CircleSprite(x=0, y=0, radius=1)
verify.verifyObject(ISprite, sprite)
这是可以通过 pytest、nose 或其他测试运行程序运行的东西,它将验证创建的精灵是否符合接口。测试通常是部分的:它不会测试任何仅在文档中提及的内容,甚至不会测试方法是否可以在没有异常的情况下调用!但是,它确实检查了正确的方法和属性是否存在。这是单元测试套件的一个很好的补充,并且至少可以防止简单的拼写错误通过测试。
评论已关闭。