python 类型化递归类和继承

r8uurelv  于 2022-11-28  发布在  Python
关注(0)|答案(1)|浏览(146)

我有以下类层次结构:

#!/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来添加额外的字段。字段'elements'和'attributes'应该分别存储派生类对象。对于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错误?

2ledvvac

2ledvvac1#

这个问题挺有意思的,我以为PEP 646的支持稍微好一点。
除非明确指定,否则我假定python 3.10和特定检查器的最新发布版本:第一个月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]))

这会产生一个错误(在源代码中注解). Here's playground
在不久的将来(甚至可以在mypymaster分支上使用,但不能在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

你可以试试here-同样的1错误。
变量attributes
现在你想保留attributes类型--它们可以是异构的,因此你需要PEP 646来继续。该类在未知数量的变量中成为泛型。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]))

Pyright表示0 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”(Citation)。你肯定不需要简单属性访问的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]))

...并且可以正常工作。很快就可以正常工作-目前mypy不完全支持Self,检查此问题会导致内部错误(崩溃),reported here by mePyright响应时没有错误。

相关问题