defaults that initialise type parameters¶
a parameter's default can decide a type parameter: when a call leaves the argument out, the type parameter is solved from the default, the same as if the default had been passed
the default only has to fit some specialization of the annotation, not every one. 1 is not a
valid T for every T, but it is for the T that a call leaving t out solves
this is purely a checker rule: the program transpiles and runs unchanged
the default is solved with the other arguments¶
a default the call falls back on is one more value for the type parameter, alongside the arguments that were passed
when an argument pins the type parameter to something the default doesn't fit, the call is an
invalid-argument-type error, with a note naming the default that took part
def first[T](items: list[T], fallback: T = 0) -> T:
return items[0] if items else fallback
def _(names: list[str]):
name = first(names, "") # fine
name = first(names) # error: `list[T]` makes `T` a `str`, and `0` isn't one
unpacked arguments¶
an unpacked argument that may be too short to reach the parameter may or may not supply it, so the default takes part alongside what it unpacks
def f[T](t: T = 1) -> T:
return t
def _(maybe: tuple[()] | tuple[str]):
x = f(*maybe) # `str | 1`: `f(*())` is `f()`
class type parameters¶
a constructor's defaults initialise the class's type parameters
class Box[T]:
def __init__(self, item: T = 1):
self.item = item
def replace(self, item: T = 1) -> T:
return item
numbers = Box() # `Box[int]`
words = Box("a") # `Box[str]`
a method's call is given the class's type parameters by the receiver, so its default is checked against them
def _(box: Box[str]):
word = box.replace() # error: the default `1` is not a `str`
empty = Box[str]() # error: the default `1` is not a `str`
some parameters¶
a some parameter opens an anonymous type parameter, and its default initialises
it like any other. another parameter annotated with that type contributes to it too
inherited defaults¶
an override that inherits a default solves it against its own annotation, not the base's
class A:
def f(self, a: int = 1) -> int:
return a
class B(A):
override def f[T](self, a: T) -> T:
return a
one = B().f() # `1`
overrides, protocols and overloads keep the default¶
a caller that holds an A and calls a.m() solves T from the default A.m declares, whatever
subclass it really holds. so an override, a protocol implementation, or a function passed where a
callable protocol is expected has to run with a default that the declared one describes
class A:
def m[T](self, t: T = 1) -> T:
return t
class B(A):
override def m[T](self, t: T = "a") -> T: # error: invalid-method-override
return t
an overload's default makes the same promise about its implementation, which is what runs. an
implementation whose default the overload's doesn't describe is an invalid-overload error
passing the function on¶
a function whose default initialises a type parameter can stand in for a callable that leaves the
argument out, and a ParamSpec that forwards its parameters keeps the default
from collections.abc import Callable
def g[T](t: T = 1) -> T:
return t
def call[R](f: Callable[[], R]) -> R:
return f()
one = call(g) # `1`
a functools.partial that leaves the parameter to the later call solves the default together with
the arguments it binds, since the later call may fall back on it
what is still an error¶
a default no specialization fits is an invalid-parameter-default error where it is written. that
includes a default for a type parameter of an enclosing function, which no call can change, and a
default whose own type names the type parameter: a default is typed as the one value made where the
def runs, which no single call's type parameter describes
def bounded[T: str](t: T = 1): ... # error: `1` is never a `str`
def outer[T](value: T):
def inner(t: T = 1): ... # error: `T` is fixed by `outer`
def first_or[T](value: T, fallback: list[T] = [1]): ... # error: `[1]` is a `list[T | int]`
an inherited default that fits no specialization of the override's annotation is reported on the override
the ... that a stub, an @overload, an @abstractmethod, a protocol member or a declaration under
if TYPE_CHECKING: writes in place of a default is not a value, so it solves nothing
limitations¶
assigning such a function to a declared callable type, c: Callable[[], str] = g, doesn't take the
default into account yet, so that assignment is accepted