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...