Named Tuples¶
As with tuples, named tuple classes require some special behaviors for type checking.
Defining Named Tuples¶
A named tuple class can be defined using a class syntax or a factory function call.
Type checkers should support the class syntax:
class Point(NamedTuple):
x: int
y: int
units: str = "meters"
Regardless of whether the class syntax or factory function call is used to define
a named tuple, type checkers should synthesize a __new__
method based on
the named tuple fields. This mirrors the runtime behavior. In the example
above, the synthesized __new__
method would look like the following:
def __new__(cls, x: int, y: int, units: str = "meters") -> Self:
...
The runtime implementation of NamedTuple
enforces that fields with default
values must come after fields without default values. Type checkers should
likewise enforce this restriction:
class Location(NamedTuple):
altitude: float = 0.0
latitude: float # Type error (previous field has a default value)
longitude: float
A named tuple class can be subclassed, but any fields added by the subclass are not considered part of the named tuple type. Type checkers should enforce that these newly-added fields do not conflict with the named tuple fields in the base class:
class PointWithName(Point):
name: str # OK
x: int # Type error (invalid override of named tuple field)
In Python 3.11 and newer, the class syntax supports generic named tuple classes. Type checkers should support this:
class Property[T](NamedTuple):
name: str
value: T
reveal_type(Property("height", 3.4)) # Revealed type is Property[float]
NamedTuple
does not support multiple inheritance. Type checkers should
enforce this limitation:
class Unit(NamedTuple, object): # Type error
name: str
The factory function call supports two variants: collections.namedtuple
and
typing.NamedTuple
. The latter provides a way to specify the types
of the fields in the tuple whereas the former does not. The namedtuple
form allows fields to be specified as a tuple or list of strings or a single
string with fields separated by whitespace or commas. The NamedTuple
functional form accepts an iterable of (name, type)
pairs.
For the namedtuple
form, all fields are assumed to be of type Any
.
A type checker may support the factory function call in its various forms:
Point1 = namedtuple('Point1', ['x', 'y'])
Point2 = namedtuple('Point2', ('x', 'y'))
Point3 = namedtuple('Point3', 'x y')
Point4 = namedtuple('Point4', 'x, y')
Point5 = NamedTuple('Point5', [('x', int), ('y', int)])
Point6 = NamedTuple('Point6', (('x', int), ('y', int)))
At runtime, the namedtuple
function disallows field names that are
illegal Python identifiers and either raises an exception or replaces these
fields with a parameter name of the form _N
. The behavior depends on
the value of the rename
argument. Type checkers may replicate this
behavior statically:
NT1 = namedtuple("NT1", ["a", "a"]) # Type error (duplicate field name)
NT2 = namedtuple("NT2", ["abc", "def"], rename=False) # Type error (illegal field name)
NT3 = namedtuple("NT3", ["abc", "def"], rename=True) # OK
NT3(abc="", _1="") # OK
The namedtuple
function also supports a defaults
keyword argument that
specifies default values for the fields. Type checkers may support this:
NT4 = namedtuple("NT4", "a b c", defaults=(1, 2))
NT4() # Type error (too few arguments)
NT4(1) # OK
Named Tuple Usage¶
The fields within a named tuple instance can be accessed by name using an
attribute access (.
) operator. Type checkers should support this:
p = Point(1, 2)
assert_type(p.x, int)
assert_type(p.units, str)
Like normal tuples, elements of a named tuple can also be accessed by index, and type checkers should support this:
assert_type(p[0], int)
assert_type(p[2], str)
Type checkers should enforce that named tuple fields cannot be overwritten or deleted:
p.x = 3 # Type error
p[0] = 3 # Type error
del p.x # Type error
del p[0] # Type error
Like regular tuples, named tuples can be unpacked. Type checkers should understand this:
x, y, units = p
assert_type(x, int)
assert_type(units, str)
x, y = p # Type error (too few values to unpack)
Assignability¶
A named tuple is assignable to a tuple
with a known length and
parameterized by types corresponding to the named tuple’s individual field
types:
p = Point(x=1, y=2, units="inches")
v1: tuple[int, int, str] = p # OK
v2: tuple[Any, ...] = p # OK
v3: tuple[int, int] = p # Type error (too few elements)
v4: tuple[int, str, str] = p # Type error (incompatible element type)
As with normal tuples, named tuples are covariant in their type parameters:
v5: tuple[float, float, str] = p # OK