[英]typing recursive class and inheritance
我有以下 class 層次結構:
#!/usr/bin/env python3
from typing import List, Optional, Tuple, Type
class Attribute:
def __init__(self, name: bytes) -> None:
self._name = name
@property
def name(self) -> bytes:
return self._name
class Element:
def __init__(self, name: bytes, attributes: Tuple[Type['Attribute'], ...], elements: Tuple['Element', ...]) -> None:
self._name = name
self._elements = elements
self._attributes = attributes
@property
def name(self) -> bytes:
return self._name
@property
def elements(self) -> Tuple['Element', ...]:
return self._elements
@property
def attributes(self) -> Tuple[Type['Attribute'], ...]:
return self._attributes
class SubAttribute1(Attribute):
def __init__(self, name: bytes, field1: bytes) -> None:
super().__init__(name)
self._afield1 = field1
class SubElement1(Element):
def __init__(self, name: bytes, attributes: Tuple[Type[Attribute], ...], elements: Tuple['Element', ...], field1: bytes, field2: bytes) -> None:
super().__init__(name, attributes, elements)
self._field1 = field1
self._field2 = field2
if __name__ == '__main__':
subE = SubElement1(b'name', None, None, b'', b'')
subA = SubAttribute1(b'name', b'field1')
subE2 = SubElement1(b'name', (subA,), (subE,), b'', b'')
print(subE2.elements[0]._field1)
print(subE2.attributes[0]._afield1)
print(type(subE2.elements[0]))
我將基類 Element 和 Attribute 子類化以添加其他字段。 “元素”和“屬性”字段應分別存儲派生類對象。 對於 SubElement1,SubElement1().elements 存儲一個包含 SubElement1 對象的元組。 一切正常,但我收到以下 mypy 錯誤:
question.py:45: error: Argument 2 to "SubElement1" has incompatible type "Tuple[SubAttribute1]"; expected "Tuple[Type[Attribute], ...]"
question.py:46: error: "Element" has no attribute "_field1"
question.py:47: error: "Type[Attribute]" has no attribute "_afield1"
如何更改代碼以消除 mypy 錯誤?
這個問題挺有意思的,我覺得PEP646支持稍微好一點。
我假設 python 3.10 和特定檢查器的最新發布版本,除非明確指定: mypy==0.991
; pyre-check==0.9.17
; pyright==1.1.281
elements
適當首先,這是解決“元素”問題但對屬性沒有幫助的(足夠簡單的)代碼:
from typing import Generic, List, Optional, Sequence, Tuple, Type, TypeVar
_Self = TypeVar('_Self', bound='Element')
class Attribute:
def __init__(self, name: bytes) -> None:
self._name = name
@property
def name(self) -> bytes:
return self._name
class Element:
def __init__(self: _Self, name: bytes, attributes: tuple[Attribute, ...], elements: Sequence[_Self]) -> None:
self._name = name
self._elements = tuple(elements)
self._attributes = attributes
@property
def name(self) -> bytes:
return self._name
@property
def elements(self: _Self) -> Tuple[_Self, ...]:
return self._elements
@property
def attributes(self) -> Tuple[Attribute, ...]:
return self._attributes
class SubAttribute1(Attribute):
def __init__(self, name: bytes, field1: bytes) -> None:
super().__init__(name)
self._afield1 = field1
class SubElement1(Element):
def __init__(self: _Self, name: bytes, attributes: tuple[Attribute, ...], elements: Sequence[_Self], field1: bytes, field2: bytes) -> None:
super().__init__(name, attributes, elements)
self._field1 = field1
self._field2 = field2
if __name__ == '__main__':
subE = SubElement1(b'name', tuple(), tuple(), b'', b'')
subA = SubAttribute1(b'name', b'field1')
subE2 = SubElement1(b'name', (subA,), (subE,), b'', b'')
print(subE2.elements[0]._field1)
print(subE2.attributes[0]._afield1) # E: "Attribute" has no attribute "_afield1" [attr-defined]
print(type(subE2.elements[0]))
這給出了一個錯誤(在源代碼中注釋)。 這里是游樂場。
在不久的將來(甚至可以在mypy
master
分支上工作,但不能在0.991
上工作)您將能夠用from typing_extensions import Self
替換_Self
並跳過注釋self
參數,如下所示:
# import from typing, if python >= 3.11
from typing_extensions import Self
class Element:
def __init__(self, name: bytes, attributes: tuple[Attribute, ...], elements: Sequence[Self]) -> None:
self._name = name
self._elements = tuple(elements)
self._attributes = attributes
您可以在這里嘗試 - 相同的 1 個錯誤。
attributes
現在您想保留attributes
類型——它們可以是異構的,因此您需要PEP646才能繼續。 class 在未知數量的變量中變得通用。 pyre
和pyright
聲稱支持這一點( mypy
不支持,該工作目前正在進行中)。 pyre
未能對下面的解決方案進行類型檢查,給出了一些虛假錯誤。 pyright
成功(雖然我個人不喜歡它,所以不建議切換)。 Pyright 沙盒是非官方的,不是最新的,在這里不起作用 - 將其復制到本地,安裝並運行pyright
進行驗證。
from typing import Generic, List, Optional, Sequence, Tuple, Type, TypeVar
from typing_extensions import Unpack, Self, TypeVarTuple
_Ts = TypeVarTuple('_Ts')
class Attribute:
def __init__(self, name: bytes) -> None:
self._name = name
@property
def name(self) -> bytes:
return self._name
class Element(Generic[Unpack[_Ts]]):
def __init__(self, name: bytes, attributes: tuple[Unpack[_Ts]], elements: Sequence[Self]) -> None:
self._name = name
self._elements = tuple(elements)
self._attributes = attributes
@property
def name(self) -> bytes:
return self._name
@property
def elements(self) -> Tuple[Self, ...]:
return self._elements
@property
def attributes(self) -> Tuple[Unpack[_Ts]]:
return self._attributes
class SubAttribute1(Attribute):
def __init__(self, name: bytes, field1: bytes) -> None:
super().__init__(name)
self._afield1 = field1
class SubElement1(Element[Unpack[_Ts]]):
def __init__(self, name: bytes, attributes: tuple[Unpack[_Ts]], elements: Sequence[Self], field1: bytes, field2: bytes) -> None:
super().__init__(name, attributes, elements)
self._field1 = field1
self._field2 = field2
if __name__ == '__main__':
subE = SubElement1(b'name', tuple(), tuple(), b'', b'')
subA = SubAttribute1(b'name', b'field1')
subE2 = SubElement1(b'name', (subA,), (subE,), b'', b'')
print(subE2.elements[0]._field1)
print(subE2.attributes[0]._afield1)
print(type(subE2.elements[0]))
Pyright
說0 errors, 0 warnings, 0 informations
, pyre
錯誤:
ƛ Found 2 type errors!
t/t.py:15:14 Undefined or invalid type [11]: Annotation `Unpack` is not defined as a type.
t/t.py:15:14 Undefined or invalid type [11]: Annotation `_Ts` is not defined as a type.
即使有實驗標志, mypy
變得完全瘋狂,如果你想看這個,請粘貼到mypy
游樂場。
attributes
但是,如果您的屬性可以用同構序列表示(例如, SubElement1
實例可以只包含SubAttribute1
),事情就簡單多了,具有常規TypeVar
的泛型就足夠了:
from typing import Generic, List, Optional, Sequence, Tuple, Type, TypeVar
_Self = TypeVar('_Self', bound='Element')
_A = TypeVar('_A', bound='Attribute')
class Attribute:
def __init__(self, name: bytes) -> None:
self._name = name
@property
def name(self) -> bytes:
return self._name
class Element(Generic[_A]):
def __init__(self: _Self, name: bytes, attributes: Sequence[_A], elements: Sequence[_Self]) -> None:
self._name = name
self._elements = tuple(elements)
self._attributes = tuple(attributes)
@property
def name(self) -> bytes:
return self._name
@property
def elements(self: _Self) -> Tuple[_Self, ...]:
return self._elements
@property
def attributes(self) -> Tuple[_A, ...]:
return self._attributes
class SubAttribute1(Attribute):
def __init__(self, name: bytes, field1: bytes) -> None:
super().__init__(name)
self._afield1 = field1
class SubElement1(Element[SubAttribute1]):
def __init__(self: _Self, name: bytes, attributes: Sequence[SubAttribute1], elements: Sequence[_Self], field1: bytes, field2: bytes) -> None:
super().__init__(name, attributes, elements)
self._field1 = field1
self._field2 = field2
if __name__ == '__main__':
subE = SubElement1(b'name', tuple(), tuple(), b'', b'')
subA = SubAttribute1(b'name', b'field1')
subE2 = SubElement1(b'name', (subA,), (subE,), b'', b'')
print(subE2.elements[0]._field1)
print(subE2.attributes[0]._afield1)
print(type(subE2.elements[0]))
這行得通。
您提供的所有代碼都稱為“用 Python 編寫 Java”(引文)。 您絕對不需要具有簡單屬性訪問權限的 getter,因為您可以隨時添加它們。 你不應該手工編寫數據類—— dataclasses
標准模塊會做得更好。 因此,您的示例確實簡化為更加簡潔和可維護的 python:
from typing import Generic, Sequence, TypeVar
from typing_extensions import Self
from dataclasses import dataclass
_A = TypeVar('_A', bound='Attribute')
@dataclass
class Attribute:
name: bytes
@dataclass
class Element(Generic[_A]):
name: bytes
attributes: Sequence[_A]
elements: Sequence[Self]
# OK, if you need different names in constructor signature and class dict
class SubAttribute1(Attribute):
def __init__(self, name: bytes, field1: bytes) -> None:
super().__init__(name)
self._afield1 = field1
# But I'd really prefer
# @dataclass
# class SubAttribute1(Attribute):
# field1: bytes
# And adjust calls below to use `field1` instead of `_afield1` - you try to expose it anyway
@dataclass
class SubElement1(Element[SubAttribute1]):
field1: bytes
field2: bytes
if __name__ == '__main__':
subE = SubElement1(b'name', tuple(), tuple(), b'', b'')
subA = SubAttribute1(b'name', b'field1')
subE2 = SubElement1(b'name', (subA,), (subE,), b'', b'')
print(subE2.elements[0].field1)
print(subE2.attributes[0]._afield1)
print(type(subE2.elements[0]))
......它有效。 好吧,很快就會工作——目前Self
在mypy
中沒有得到完全支持,檢查這會導致內部錯誤(崩潰),由我在這里報告。 Pyright
響應沒有錯誤。 更新:錯誤已在mypy
master 上修復,上面的示例進行了類型檢查。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.