[英]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.