簡體   English   中英

暗示一個類正在實現協議的正確方法?

[英]Correct way to hint that a class is implementing a Protocol?

在改進我的 Python 開發工作的道路上。 我有興趣在 CI/deb 構建時測試使用協議定義的接口,這樣如果接口實際上沒有由類實現,我們將在單元測試運行后立即知道。

我的方法是使用 Protocol 鍵入並使用實現 runtime_checkable 來構建單元測試。 這行得通,但團隊就如何表明一個具體是在不破壞 runtime_checkable 的情況下實現一個協議進行了一些爭論。 在 C++/Java 中,您需要繼承來指示接口的實現,但在 Python 中,您不一定需要繼承。 討論的中心是我們是否應該從協議接口類繼承。

最后考慮這個代碼示例,它提供了問題的大部分要點。 我們正在考慮 Shape 並指示如何向未來的開發人員暗示 Shape 正在提供 IShape,但是這樣做會導致 isinstance 的 runtime_checkable 版本無法用於單元測試。

這里有兩條改進途徑:

我們可以找到一種更好的方法來暗示 Shape 實現了不涉及直接繼承的 IShape。 我們可以找到一種更好的方法來檢查接口是否在測試 deb 包構建時實現。 也許 runtime_checkable 是錯誤的想法。

有人獲得有關如何更好地使用 Python 的指導嗎? 謝謝!


from typing import (
    Protocol,
    runtime_checkable
)
import dataclasses

@runtime_checkable
class IShape(Protocol):
    x: float


@dataclasses.dataclass
class Shape(IShape):
    foo:float = 0.

s  = Shape()
# evaluates as True but doesnt provide the interface. Undermines the point of the run-time checkable in unit testing
assert isinstance(s, IShape)
print(s.x)  # Error.  Interface wasnt implemented




#
# Contrast with this assert
#
@dataclasses.dataclass
class Goo():
    x:float = 1

@dataclasses.dataclass
class Hoo():
    foo: float = 1

g = Goo()
h = Hoo()
assert isinstance(g, IShape)  # asserts as true
# but because it has the interface and not because we inherited.
print(g.x)


assert isinstance(h, IShape)  # asserts as False which is what we want

在談論靜態類型檢查時,有助於理解子類型與子類不同的概念。 (在 Python 中,類型和類是同義詞;在mypy等工具實現的類型系統中則不然。)

類型T是類型S名義子類型,如果我們明確地說它是。 子類化是名義子類化的一種形式:如果(但不僅是) TS的子類, TS的子類型。

類型T是類型S結構子類型,如果它關於T本身的某些內容與S兼容。 協議是 Python 對結構子類型化的實現。 Shape不需要是IShape的名義子類型(通過子類化)才能成為IShape的結構子類型(通過具有x屬性)。

因此,將IShape定義為Protocol而不僅僅是Shape的超類的重點是支持結構子類型化並避免名義子類型化的需要(以及繼承可能引入的所有問題)。

class IShape(Protocol):
    x: float


# A structural subtype of IShape
# Not a nominal subtype of IShape
class Shape:
    def __init__(self):
        self.x = 3

# Not a structural subtype of IShape
class Unshapely:
    def __init__(self):
        pass


def foo(v: IShape):
    pass

foo(Shape())  # OK
foo(Unshapely())  # Not OK

那么結構子類型是否可以替代名義子類型? 一點也不。 繼承有它的用途,但是當它是你唯一的子類型化方法時,它會被不恰當地使用。 一旦您在類型系統中區分了結構子類型和名義子類型,就可以使用適合您實際需要的子類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM