繁体   English   中英

输入递归 class 和 inheritance

[英]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 在未知数量的变量中变得通用。 pyrepyright声称支持这一点( 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]))

Pyright0 errors, 0 warnings, 0 informationspyre错误:

ƛ 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]))

......它有效。 好吧,很快就会工作——目前Selfmypy中没有得到完全支持,检查这会导致内部错误(崩溃),由我在这里报告 Pyright响应没有错误。 更新:错误已在mypy master 上修复,上面的示例进行了类型检查。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM