Tuples

Thetuple class has some special behaviors and properties that make itdifferent from other classes from a typing perspective. The most obviousdifference is thattuple is variadic – it supports an arbitrary numberof type arguments. At runtime, the sequence of objects contained within thetuple is fixed at the time of construction. Elements cannot be added, removed,reordered, or replaced after construction. These properties affect subtypingrules and other behaviors as described below.

Tuple Type Form

The type of a tuple can be expressed by listing the element types. Forexample,tuple[int,int,str] is a tuple containing anint, anotherint, and astr.

The empty tuple can be annotated astuple[()].

Arbitrary-length homogeneous tuples can be expressed using one type and anellipsis, for exampletuple[int,...]. This type is equivalent to a unionof tuples containing zero or moreint elements (tuple[()]|tuple[int]|tuple[int,int]|tuple[int,int,int]|...).Arbitrary-length homogeneous tuples are sometimes referred to as “unboundedtuples”. Both of these terms appear within the typing spec, and they refer tothe same concept.

The typetuple[Any,...] is special in that it isconsistent withall tuple types, andassignable to a tuple of any length. This isuseful for gradual typing. The typetuple (with no type arguments provided)is equivalent totuple[Any,...].

Arbitrary-length tuples have exactly two type arguments – the type andan ellipsis. Any other tuple form that uses an ellipsis is invalid:

t1:tuple[int,...]# OKt2:tuple[int,int,...]# Invalidt3:tuple[...]# Invalidt4:tuple[...,int]# Invalidt5:tuple[int,...,int]# Invalidt6:tuple[*tuple[str],...]# Invalidt7:tuple[*tuple[str,...],...]# Invalid

Unpacked Tuple Form

An unpacked form oftuple (using an unpack operator*) can be usedwithin a tuple type argument list. For example,tuple[int,*tuple[str]]is equivalent totuple[int,str]. Unpacking an unbounded tuple preservesthe unbounded tuple as it is. That is,*tuple[int,...] remains*tuple[int,...]; there’s no simpler form. This enables us to specifytypes such astuple[int,*tuple[str,...],str] – a tuple type where thefirst element is guaranteed to be of typeint, the last element isguaranteed to be of typestr, and the elements in the middle are zero ormore elements of typestr. The typetuple[*tuple[int,...]] isequivalent totuple[int,...].

If an unpacked*tuple[Any,...] is embedded within another tuple, thatportion of the tuple isconsistent with any tuple of any length.

Only one unbounded tuple can be used within another tuple:

t1:tuple[*tuple[str],*tuple[str]]# OKt2:tuple[*tuple[str,*tuple[str,...]]]# OKt3:tuple[*tuple[str,...],*tuple[int,...]]# Type errort4:tuple[*tuple[str,*tuple[str,...]],*tuple[int,...]]# Type error

An unpacked TypeVarTuple counts as an unbounded tuple in the context of this rule:

deffunc[*Ts](t:tuple[*Ts]):t5:tuple[*tuple[str],*Ts]# OKt6:tuple[*tuple[str,...],*Ts]# Type error

The* syntax requires Python 3.11 or newer. For older versions of Python,thetyping.Unpackspecial form can be used:tuple[int,Unpack[tuple[str,...]],int].

Unpacked tuples can also be used for*args parameters in a functionsignature:deff(*args:*tuple[int,str]):.... Unpacked tuplescan also be used for specializing generic classes or type variables that areparameterized using aTypeVarTuple. For more details, see*args as a Type Variable Tuple.

Type Compatibility Rules

Because tuple contents are immutable, the element types of a tuple are covariant.For example,tuple[bool,int] is a subtype oftuple[int,object].

As discussed above, a homogeneous tuple of arbitrary length is equivalentto a union of tuples of different lengths. That meanstuple[()],tuple[int] andtuple[int,*tuple[int,...]] are all subtypes oftuple[int,...]. The converse is not true;tuple[int,...] is not asubtype oftuple[int].

The typetuple[Any,...] isconsistent with any tuple:

deffunc(t1:tuple[int],t2:tuple[int,...],t3:tuple[Any,...]):v1:tuple[int,...]=t1# OKv2:tuple[Any,...]=t1# OKv3:tuple[int]=t2# Type errorv4:tuple[Any,...]=t2# OKv5:tuple[float,float]=t3# OKv6:tuple[int,*tuple[str,...]]=t3# OK

The length of a tuple at runtime is immutable, so it is safe for type checkersto use length checks to narrow the type of a tuple:

deffunc(val:tuple[int]|tuple[str,str]|tuple[int,*tuple[str,...],int]):iflen(val)==1:# Type can be narrowed to tuple[int].reveal_type(val)# tuple[int]iflen(val)==2:# Type can be narrowed to tuple[str, str] | tuple[int, int].reveal_type(val)# tuple[str, str] | tuple[int, int]iflen(val)==3:# Type can be narrowed to tuple[int, str, int].reveal_type(val)# tuple[int, str, int]

This property may also be used to safely narrow tuple types within amatchstatement that uses sequence patterns.

If a tuple element is a union type, the tuple can be safely expanded into aunion of tuples. For example,tuple[int|str] is equivalent totuple[int]|tuple[str]. If multiple elements are union types, full expansionmust consider all combinations. For example,tuple[int|str,int|str] isequivalent totuple[int,int]|tuple[int,str]|tuple[str,int]|tuple[str,str].Unbounded tuples cannot be expanded in this manner.

Type checkers may safely use this equivalency rule when narrowing tuple types:

deffunc(subj:tuple[int|str,int|str]):matchsubj:casex,str():reveal_type(subj)# tuple[int | str, str]casey:reveal_type(subj)# tuple[int | str, int]

Thetuple class derives fromSequence[T_co] whereT_co is a covariant(non-variadic) type variable. The specialized type ofT_co should be computedby a type checker as a supertype of all element types.For example,tuple[int,*tuple[str,...]] is a subtype ofSequence[int|str] orSequence[object].

A zero-length tuple (tuple[()]) is a subtype ofSequence[Never].