藉助 zope.interface 深刻了解 Python 接口

Zope.interface 能夠幫助聲明存在哪些接口,是由哪些對象提供的,以及如何查詢這些信息。python

Snake charmer cartoon with a yellow snake and a blue snake
Snake charmer cartoon with a yellow snake and a blue snake

zope.interface 庫能夠克服 Python 接口設計中的歧義性。讓咱們來研究一下。linux

隱式接口不是 Python 之禪

Python 之禪 很寬鬆,可是有點自相矛盾,以致於你能夠用它來例證任何東西。讓咱們來思考其中最著名的原則之一:「顯示勝於隱式」。git

傳統上,在 Python 中會隱含的一件事是預期的接口。好比函數已經記錄了它指望一個「類文件對象」或「序列」。可是什麼是類文件對象呢?它支持 .writelines嗎?.seek 呢?什麼是一個「序列」?是否支持步進切片,例如 a[1:10:2]github

最初,Python 的答案是所謂的「鴨子類型」,取自短語「若是它像鴨子同樣行走,像鴨子同樣嘎嘎叫,那麼它可能就是鴨子」。換句話說,「試試看」,這多是你能獲得的最具隱式的表達。ruby

爲了使這些內容顯式地表達出來,你須要一種方法來表達指望的接口。Zope Web 框架是最先用 Python 編寫的大型系統之一,它迫切須要這些東西來使代碼明確呈現出來,例如,指望從「相似用戶的對象」得到什麼。bash

zope.interface 由 Zope 開發,但做爲單獨的 Python 包發佈。Zope.interface 能夠幫助聲明存在哪些接口,是由哪些對象提供的,以及如何查詢這些信息。框架

想象編寫一個簡單的 2D 遊戲,它須要各類東西來支持精靈界面(LCTT 譯註:「精靈 Sprite」是指遊戲面板中各個組件)。例如,表示一個邊界框,但也要表示對象什麼時候與一個框相交。與一些其餘語言不一樣,在 Python 中,將屬性訪問做爲公共接口一部分是一種常見的作法,而不是實現 getter 和 setter。邊界框應該是一個屬性,而不是一個方法。函數

呈現精靈列表的方法可能相似於:單元測試

def render_sprites(render_surface, sprites):
    """     sprites 應該是符合 Sprite 接口的對象列表:     * 一個名爲 "bounding_box" 的屬性,包含了邊界框     * 一個名爲 "intersects" 的方法,它接受一個邊界框並返回 True 或 False     """
    pass # 一些作實際渲染的代碼
複製代碼

該遊戲將具備許多處理精靈的函數。在每一個函數中,你都必須在隨附文檔中指定預期。測試

此外,某些函數可能指望使用更復雜的精靈對象,例如具備 Z 序的對象。咱們必須跟蹤哪些方法須要 Sprite 對象,哪些方法須要 SpriteWithZ 對象。

若是可以使精靈是顯式而直觀的,這樣方法就能夠聲明「我須要一個精靈」,並有個嚴格定義的接口,這不是很好嗎?來看看 zope.interface

from zope import interface

class ISprite(interface.Interface):

    bounding_box = interface.Attribute(
        "邊界框"
    )

    def intersects(box):
        "它和一個框相交嗎?"
複製代碼

乍看起來,這段代碼有點奇怪。這些方法不包括 self,而包含 self 是一種常見的作法,而且它有一個屬性。這是在 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):
        # 當且僅當至少一個角在圓內時,方框與圓相交
        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 或其餘測試框架運行,它將驗證建立的精靈是否符合接口。測試一般是局部的:它不會測試僅在文檔中說起的內容,甚至不會測試方法是否能夠在沒有異常的狀況下被調用!可是,它會檢查是否存在正確的方法和屬性。這是對單元測試套件一個很好的補充,至少能夠防止簡單的拼寫錯誤經過測試。


via: opensource.com/article/19/…

做者:Moshe Zadka 選題:lujun9972 譯者:MjSeven 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章
相關標籤/搜索