# Tuples

Hint Accepted value
tuple[int, str] (3, "red")
tuple[int] (3,)
tuple[float, ...] Any number of exact floats
tuple[()] () only

typing.Tuple[...] supports the same forms. Bare tuples, unpacked hints and slots containing only None are rejected. tuple[int | None, ...] is valid. Positions may contain nested dataclasses, sequences, unions and type atoms.

from dataclasses import dataclass
from typing import Annotated
from pytypehint import Max, Min, struct_of

Channel = Annotated[float, Min(0.0), Max(1.0)]

@dataclass
class Swatch:
    color: tuple[Channel, Channel, Channel, Channel]
    samples: Annotated[tuple[float, ...], Min(1), Max(16)]

schema = struct_of(Swatch)
value = schema.build({"color": (0.2, 0.8, 0.4, 1.0), "samples": (0.5,)})

Tuple-level Min and Max bound length with nonnegative integers, inclusive only. Bounds excluding a fixed tuple's size fail at compilation. Extra is supported; field atoms cannot apply to positions. Validation requires exact tuples, checks length before contents and reports failing indexes.

Portable arrays become tuples through decode. Actual Python tuples are left untouched, including their contents. Multiple tuple variants require {"$type": "tuple[int]", "$value": (3,)} for Python input, or an array payload for portable input. For list[int] | tuple[int, ...], a bare portable array stays a list; name the tuple option to restore it.

The public Tuple shape uses items, a tuple of option tuples, and variadic:

from pytypehint import Int, Str, Tuple

pair = Tuple(items=((Int(),), (Str(),)))
sequence = Tuple(items=((Int(),),), variadic=True)
empty = Tuple(items=())

Fixed shapes have one slot per position; variadic shapes have one repeated slot. min, max and extras describe the whole tuple. The portable contract uses items for fixed positions and item for repeated items. Tuple defaults serialize as arrays and rematerialize their contents at each serving.