簡體   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