Python 通用迭代器注释

问题描述 投票:0回答:2

我正在尝试注释一个仅返回两个值的迭代器,

T
cls[T]

目前我的注释是这样的:

from __future__ import annotations
import typing


class Node(typing.Generic[T]):
    def __init__(self, value: T, next: typing.Optional[Node[T]] = None) -> None:
        self.value = value 
        self.next = next

    def __iter__(self) -> typing.Iterator[typing.Union[T, Node[T]]]:
        yield from (self.value, self.next)

此代码可以正常运行,没有任何错误。然而,我希望利用这个 dunder 方法将always产生两个值来简化用户的过程。

事实上,用户必须处理以下问题:

one = Node[int](1, Node[int](2))
value, next = one # value = 1, next = Node(2) (i.e one.next)

# This is a typing error because next can be either int or Node[int] 
# and 'two' is expected to be Node[int]:
two: Node[int] = next

# we can fix this by doing type narrowing
assert isinstance(next, Node)
two = next # now the error should be gone

所以基本上我想利用这样一个事实,即

__iter__
返回的第二个东西始终是
Node[T]
类型,以避免必须进行类型缩小。

我知道我必须更改方法的返回注释中的

typing.Union[T, Node[T]]
,但我不知道将其更改为什么。

python generics python-typing mypy
2个回答
1
投票

无法使用通用

Iterator
进行注释。这是一个恰好需要 one 类型参数的类。 (当前typeshed源)这意味着其__next__方法返回的
每个
值都必须具有相同的类型。

您正在对

self.value, self.next
的元组调用迭代器协议。元组具有任意数量的类型参数(请参阅此处),但其迭代器仍必须恰好有一个。这实际上经常导致打字问题。

由于您似乎打算让您的

Node
类本质上模拟
tuple
接口,这可能是极少数情况之一,最好直接从它继承。显然,
tuple
还会为您提供可迭代协议,因此您仍然可以像以前一样解压它,但如果您正确执行所有操作,则应该正确推断类型。

这是一个完整的工作示例:

from __future__ import annotations
from typing import TypeVar, Optional


T = TypeVar("T")


class Node(tuple[T, "Node[T]"]):
    def __new__(cls, value: T, next_: Optional[Node[T]] = None) -> Node[T]:
        return tuple.__new__(cls, (value, next_))

    def __init__(self, value: T, next_: Optional[Node[T]] = None) -> None:
        self.value = value
        self.next = next_


if __name__ == "__main__":
    node_1 = Node(1, Node(2))
    val: int
    node_2: Node[int]
    val, node_2 = node_1

这通过了

mypy --strict
,没有任何问题。


作为一个不相关的旁注,我建议不要使用内置名称,例如

next

另请注意,在初始化时,您无需为

Node
指定类型参数,因为它会自动 bound 到传递给
value
参数的类型。


-2
投票

for while 是可以用作通用迭代器注释的循环

© www.soinside.com 2019 - 2024. All rights reserved.