[Python] How to nest generic type definitions? [duplicate]

Discussão em 'Python' iniciado por Stack, Fevereiro 8, 2025.

  1. Stack

    Stack Membro Participativo

    Say I have:

    • class A
    • class B that is parameterized by a type with upper bound A
    • class C that is parameterized by a type with upper bound B.

    How should I define these classes in Python 3.12 such that static type checkers are most succesful/correct?

    class A:
    ...

    class B[T: A]:
    ...

    class C[T: B]:
    ...


    Or should I use C[T1: A, T2: B]? I understood that C[T: B[T: A]] is not allowed. Would a separate TypeVar bound to B[A] work?

    To make it little less abstract and show a use case for this:

    • A could be a vector type which I could subtype as a 2D or 3D vector.
    • B could be a geometrical shape, with subtypes like line and arc, in 2D, 3D, etc. flavors.
    • C could be a concatenation of shapes, thus build from these 2D/3D lines/arcs.
    Here is a concrete executable example:
    from typing import Self, Type, cast, reveal_type

    # Basic structure:

    class Vector:
    identity: 'Vector'

    class Primitive[V: Vector]:
    def __init__(self, b: V, e: V):
    self.b, self.e = b, e

    class Line[V: Vector](Primitive[V]): pass

    class Arc[V: Vector](Primitive[V]):
    def __init__(self, b: V, e: V, r: float):
    super().__init__(b, e)
    self.r = r

    class PathSegment[V: Vector](Primitive[V]):
    def __init__(self, e: V, previous: Self | None = None):
    vector_type = cast(Type[V], e.__class__)
    b = previous.e if previous else vector_type.identity
    super().__init__(b=b, e=e)

    class LineSegment[V: Vector](Line[V], PathSegment[V]): pass
    class ArcSegment[V: Vector](Arc[V], PathSegment[V]): pass

    class Path[S: PathSegment, V: Vector]:
    def __init__(self, segments: list):
    self.segments = segments

    @property
    def b(self) -> V:
    return self.segments[0].b

    # 2D and 3D variants:

    class Vector2D(Vector):
    def __init__(self, x: float, y: float):
    self.v = (x, y)
    Vector2D.identity = Vector2D(0, 0)

    class Vector3D(Vector):
    def __init__(self, x: float, y: float, z: float):
    self.v = (x, y, z)
    Vector3D.identity = Vector3D(0, 0, 0)

    class Line2D(Line[Vector2D]): pass
    class Line3D(Line[Vector3D]): pass
    class Arc2D(Arc[Vector2D]): pass
    class Arc3D(Arc[Vector3D]): pass
    class PathSegment2D(PathSegment[Vector2D]): pass
    class PathSegment3D(PathSegment[Vector3D]): pass
    class LineSegment2D(LineSegment[Vector2D]): pass
    class LineSegment3D(LineSegment[Vector3D]): pass
    class ArcSegment2D(ArcSegment[Vector2D]): pass
    class ArcSegment3D(ArcSegment[Vector3D]): pass
    class Path2D(Path[PathSegment2D, Vector2D], Vector2D): pass
    class Path3D(Path[PathSegment3D, Vector3D], Vector3D): pass

    p1 = Path2D(segments=[LineSegment2D(e=Vector2D(2, 3))])
    # Complaints about this line:
    # pycharm: Expected type 'list[PathSegment2D]' (matched generic type 'list[S ≤: PathSegment]'), got 'list[LineSegment2D[Vector2D]]' instead
    # pyright: Argument of type "list[LineSegment2D]" cannot be assigned to parameter "segments" of type "list[PathSegment2D]"
    # in function "__init__". "LineSegment2D" is not assignable to "PathSegment2D" (reportArgumentType)

    # Mix-up of types not detected.
    class WrongPathClass(Path[PathSegment2D, Vector3D], Vector2D): pass

    Continue reading...

Compartilhe esta Página