Parameter specification and type variable tuple should support bounds

previous step: variance

i propose new syntax and semantics for bounds on tvt/ps type parameters:

# typical forms
class A[T: int, *Ts: int, **P: [int]]: ...
A[
    str, # error
    int, # ok, matching Ts bound
    str, # error, not matching Ts bound
    [str], # error, not matching P bound
]

A[
    int, # ok
    int, # ok, matching Ts bound
    bool, # ok, matching Ts bound
    [int], # ok, matching P bound
]

# tvt unpacked form
class B[*Ts: *tuple[int, str]]: ...

B[
    int, # ok
    int, # error, not subtype of str
    int, # error, too many types
]

this behaviour is supported in pycharm 2026.1.2

bounds will require a syntax change in cpython

cpython pr: gh-148945: add support for bounds on type variable tuples and paramet… by KotlinIsland · Pull Request #148946 · python/cpython · GitHub

3 Likes

Awesome; this will help a lot for NumPy.

For example, we currently cannot generally express functions that e.g. add or remove a dimension from an array, like numpy.expand_dims (docs), because the shape type has tuple[int, ...] as an upper bound. So instead, we work around this by adding overloads for the first couple of dimensions: numpy/numpy/lib/_shape_base_impl.pyi at a253537e7a06e8e41a96d17b3f0ece62d93071ad · numpy/numpy · GitHub
But as you can see, that can become pretty messy, and doesn’t work for higher-dimensional arrays.

With this proposal, we’d be able to get rid of most overloads. For example for the axis: int case we could write:

def expand_dims[*AxisTs: int, DTypeT: np.dtype](
    a: np.ndarray[tuple[*AxisTs], DTypeT],
    axis: int,
) -> np.ndarray[tuple[*AxisTs, int], DTypeT]

There are many more examples like these in NumPy that we could sigificantly simplify with this. These include all reduction functions, for example np.prod (src) and np.sum (src) that currently counts 23 (!) overloads each.

1 Like

What does it mean for a ParamSpec to have a bound? Is that specified somewhere? I don’t see it in the typing documentation, and I’m struggling to read the syntax in the OP intuitively.

It is not specified; the spec currently does not allow bounds for ParamSpec or TypeVarTuple. @KotlinIsland is proposing to change that.

unpacking in a bound will require a syntax change in cpython

Not just unpacking; native generics do not allow bounds for ParamSpec or TypeVarTuple. Changing this would be a syntax change that we can’t make until Python 3.16.

3 Likes

Thanks for clarifying. In that case, I would love some additional elaboration on what’s being proposed for bounds on ParamSpec. I understand the examples for TypeVarTuple and see the value there!

Thanks to typing_extensions that shouldn’t matter much for NumPy :slight_smile: