Python 的递归类型被破坏

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

我有一个类型别名,我称之为

point2d
和一个相关的递归类型别名。

point2d = tuple[float, float]

point2d_nested = (
    point2d
    | list["point2d_nested"]
    | tuple["point2d_nested", ...]
)

现在我想编写一个函数,可以按一个因子缩放任何嵌套结构中的点。例如,

scale_nested((1,1), 2) == (2,2)
scale_nested([(1,1), (1,1)]) == [(2,2), (2,2)]
。这是一个实现:

def scale_nested(points: point2d_nested, factor: float) -> point2d_nested:
    """
    Scales a list of lists of points by a given factor.
    Weird things happen if you supply 3D points or if your point is not a tuple.
    """
    if not points:
        return points

    # We've bottomed out at a point
    if (
        isinstance(points, tuple)
        and len(points) == 2
        and isinstance(points[0], (int, float))
        and isinstance(points[1], (int, float))
    ):
        return points[0] * factor, points[1] * factor

    if isinstance(points, list):
        return [scale_nested(p, factor) for p in points]

    if isinstance(points, tuple):
        return tuple(
            scale_nested(p, factor) for p in points if not isinstance(p, (int, float))
        )

    raise ValueError(f"Something is an invalid type: {points}")

不幸的是,这不起作用:

knots = [((1.0, 2.0),)]
scale_nested(knots, 2) # <- knots look red
Argument of type "list[tuple[tuple[float, float]]]" cannot be assigned to parameter "points" of type "point2d_nested" in function "scale_nested"
  Type "list[tuple[tuple[float, float]]]" cannot be assigned to type "point2d_nested"
    "list[tuple[tuple[float, float]]]" is incompatible with "point2d"
    "list[tuple[tuple[float, float]]]" is incompatible with "list[point2d_nested]"
      Type parameter "_T@list" is invariant, but "tuple[tuple[float, float]]" is not the same as "point2d_nested"
      Consider switching from "list" to "Sequence" which is covariant
    "list[tuple[tuple[float, float]]]" is incompatible with "tuple[point2d_nested, ...]"PylancereportGeneralTypeIssues
(variable) knots: list[tuple[tuple[float, float]]]
python python-typing pyright
1个回答
0
投票

根据 Pyright/Pylance 的建议,您的代码将按原样使用

Sequence
而不是
list
:

游乐场链接

from collections.abc import Sequence

point2d_nested = (
    point2d
    | Sequence["point2d_nested"]
    | tuple["point2d_nested", ...]
)
knots = [((1.0, 2.0),)]  # list[tuple[tuple[float, float]]]
scale_nested(knots, 2)   # fine

但是,请注意

scale_nested(knots, 2)
的类型。显示为:

tuple[float, float] | Sequence[point2d_nested] | tuple[point2d_nested, ...]

...也称为

point2d_nested

但是,我们可以做得更好。使用

TypeVar
告诉类型检查器,返回类型将与输入类型完全相同,只要它与
point2d_nested
兼容即可:

游乐场链接

T = TypeVar('T', bound = point2d_nested)

def scale_nested(points: T, factor: float) -> T:
    ...
knots_1 = [((1.0, 2.0),)]
reveal_type(scale_nested(knots_1, 2))  # list[tuple[tuple[float, float]]]

knots_2 = ((1.2, 3.4), (5.6, 7.8))
reveal_type(scale_nested(knots_2, 2))  # tuple[tuple[float, float], tuple[float, float]]

knots_3 = ([(1.2, 3.4)], [(5.6, 7.8), [(9.1, 2.3)]])
reveal_type(scale_nested(knots_3, 2))  # tuple[list[tuple[float, float]], list[tuple[float, float] | list[tuple[float, float]]]]

knots_4 = [('foo', 'bar')]
reveal_type(scale_nested(knots_4, 2))  # error

knots_5 = ([(1.2, 3.4)], [(5.6, 7.8), [9.1, 2.3]])
reveal_type(scale_nested(knots_5, 2))  # error
© www.soinside.com 2019 - 2024. All rights reserved.