[英]Correct way of implementing a Python class that calls external functions on attribute update
[英]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
的名義子類型,如果我們明確地說它是。 子類化是名義子類化的一種形式:如果(但不僅是) T
是S
的子類, T
是S
的子類型。
類型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.