bounds on a variadic pack¶
a variadic type parameter may carry an upper bound, and the star count on the bound decides what it constrains. an unstarred bound bounds every member of the pack; a starred one — taking the same star count the pack was declared with — bounds the pack as a whole:
class EveryElement[*Ts: int]: ...
class WholeTuple[*Ts: *(int, str)]: ...
class EveryField[**Kwargs: int]: ...
class WholeShape[**Kwargs: **{"a": int}]: ...
this mirrors the value-parameter forms exactly, the way the rest of the
type parameter list does: *args: int types each argument while *args: *Ts
types the whole run, and **kwargs: int types each value while **kwargs: **Kwargs types the
whole mapping
CPython rejects a bound on a TypeVarTuple and on a ParamSpec, so all four forms are .by-only
element-wise¶
*Ts: int requires every element of the pack to be a subtype of int:
**Kwargs: int is the same for a keyword-variadic pack — every field's
type is bounded, and the field names are unconstrained:
class A[**Kwargs: int]: ...
def ok(a: A[x=int, y=bool]): ...
def bad(a: A[x=int, y=str]): ... # error
whole-pack¶
*Ts: *X is an ordinary assignability check against the packed tuple, so X constrains the
pack's length as well as its elements:
class A[*Ts: *(int, str)]: ...
def ok(a: A[int, str]): ...
def narrower(a: A[bool, str]): ... # a tuple is covariant in its elements
def too_short(a: A[int]): ... # error
def wrong_element(a: A[int, bytes]): ... # error
a variable-length bound admits any length:
**Kwargs: **X names the fields the pack must have. X is a
dict literal type or a TypedDict; every field it names has to be
present with an assignable type. extra fields are what an upper bound permits:
class A[**Kwargs: **{"a": int}]: ...
def ok(a: A[a=int]): ...
def extra(a: A[a=int, b=str]): ...
def wrong_type(a: A[a=str]): ... # error
def missing(a: A[b=str]): ... # error
inferred packs¶
a pack solved from a call is checked where it is solved, not only where it is written out:
class A[**Kwargs: int]:
init(**kwargs: **Kwargs)
a = A(x=1, y=True) # A[x=int, y=bool]
b = A(x=1, y="s") # error
a pack bound is not a type bound¶
an ordinary type parameter's bound is an upper bound on the type it stands for, so an
unspecialized T behaves like its bound — T: int supports +, and T is assignable to int.
a pack's bound is not that: the pack's value is a tuple or a field mapping, and the bound
describes its members or its shape. an unspecialized *Ts or **Kwargs therefore behaves exactly
as it does without a bound, and the bound is checked only where the pack is specialized
lowering¶
python has no bound on either kind of pack, so the bound is erased:
transpiles to:
the bound is checked against the .by source, not the emitted python — the same way
bound ranges and type mappings are
see also¶
- generics — the type parameter forms
- keyword-variadic packs — what
**Kwargsdeclares - dict literal types — the
{"a": int}bound spelling - type parameter bound ranges —
T: Lower..Upperon an ordinary type parameter - type mappings —
T in (int, str)