3.8. Typing NamedTuple

  • NamedTuple is a class that allows you to create a tuple with named fields.

  • NamedTuple is immutable.

3.8.1. SetUp

>>> from typing import NamedTuple

3.8.2. Problem

>>> a: tuple = ('Mark', 'Watney', 41)
>>> b: tuple[str,str,int] = ('Mark', 'Watney', 41)

3.8.3. Solution

>>> class User(NamedTuple):
...     firstname: str
...     lastname: str
...     age: int
>>>
>>>
>>> a: User = User('Mark', 'Watney', 41)
>>> b: User = User(firstname='Mark', lastname='Watney', age=41)

3.8.4. Default

>>> class User(NamedTuple):
...     firstname: str
...     lastname: str
...     age: int = None
>>>
>>>
>>> a: User = User(firstname='Mark', lastname='Watney', age=41)
>>> b: User = User(firstname='Mark', lastname='Watney')

3.8.5. Field Access

  • You can access fields by name or index.

>>> class User(NamedTuple):
...     firstname: str
...     lastname: str
>>>
>>>
>>> mark: User = User(firstname='Mark', lastname='Watney')
>>>
>>> mark.firstname
'Mark'
>>>
>>> mark.lastname
'Watney'

3.8.6. Is Instance

  • NamedTuple is a subclass of tuple

>>> class User(NamedTuple):
...     firstname: str
...     lastname: str
>>>
>>>
>>> mark: User = User(firstname='Mark', lastname='Watney')
>>>
>>> isinstance(mark, User)
True
>>>
>>> isinstance(mark, tuple)
True

3.8.7. Use Case - 1

>>> class Point(NamedTuple):
...     x: int
...     y: int
...     z: int = 0
>>>
>>> a = Point(x=1, y=2)
>>> b = Point(x=1, y=2, z=3)

3.8.8. Use Case - 2

Point 2d:

>>> class Point(NamedTuple):
...     x: int
...     y: int
>>>
>>> pt = Point(x=1, y=2)

Point 3d:

>>> class Point(NamedTuple):
...     x: int
...     y: int
...     z: int = 0
>>>
>>> pt = Point(x=1, y=2)

3.8.9. Use Case - 3

>>> class User(NamedTuple):
...     id: int
...     firstname: str
...     lastname: str
...     age: int
...     height: int | float
...     weight: int | float
...     is_astronaut: bool
...     is_assigned: bool
...     mission: str | None

3.8.10. Use Case - 4

>>> class Point(NamedTuple):
...     x: int = 0
...     y: int = 0
>>>
>>>
>>> class Position:
...     position: Point
...
...     def __init__(self, initial_position: Point = Point()):
...         self.position = initial_position
...
...     def set_position(self, position: Point) -> None:
...         self.position = position
...
...     def get_position(self) -> Point:
...         return self.position
>>>
>>>
>>> current = Position()
>>>
>>> current.get_position()
Point(x=0, y=0)
>>>
>>> current.set_position(Point(1, 2))
>>>
>>> current.get_position()
Point(x=1, y=2)

3.8.11. Use Case - 5

>>> class GeographicCoordinate(NamedTuple):
...     latitude: float
...     longitude: float
>>>
>>>
>>> locations: list[tuple[float,float]] = [
...     (25.91375, -60.15503),
...     (-11.01983, -166.48477),
...     (-11.01983, -166.48477),
... ]
>>>
>>> locations: list[GeographicCoordinate] = [
...     GeographicCoordinate(25.91375, -60.15503),
...     GeographicCoordinate(-11.01983, -166.48477),
...     GeographicCoordinate(-11.01983, -166.48477),
... ]
>>>
>>> locations: list[GeographicCoordinate] = [
...     GeographicCoordinate(latitude=25.91375, longitude=-60.15503),
...     GeographicCoordinate(latitude=-11.01983, longitude=-166.48477),
...     GeographicCoordinate(latitude=-11.01983, longitude=-166.48477),
... ]

3.8.12. Use Case - 6

>>> from itertools import starmap
>>> from pprint import pprint
>>>
>>> DATA = [
...     ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
...     (5.8, 2.7, 5.1, 1.9, 'virginica'),
...     (5.1, 3.5, 1.4, 0.2, 'setosa'),
...     (5.7, 2.8, 4.1, 1.3, 'versicolor'),
...     (6.3, 2.9, 5.6, 1.8, 'virginica'),
...     (6.4, 3.2, 4.5, 1.5, 'versicolor'),
...     (4.7, 3.2, 1.3, 0.2, 'setosa'),
...     (7.0, 3.2, 4.7, 1.4, 'versicolor'),
...     (7.6, 3.0, 6.6, 2.1, 'virginica'),
...     (4.9, 3.0, 1.4, 0.2, 'setosa'),
...     (4.9, 2.5, 4.5, 1.7, 'virginica'),
...     (7.1, 3.0, 5.9, 2.1, 'virginica'),
...     (4.6, 3.4, 1.4, 0.3, 'setosa'),
...     (5.4, 3.9, 1.7, 0.4, 'setosa'),
...     (5.7, 2.8, 4.5, 1.3, 'versicolor'),
...     (5.0, 3.6, 1.4, 0.3, 'setosa'),
...     (5.5, 2.3, 4.0, 1.3, 'versicolor'),
...     (6.5, 3.0, 5.8, 2.2, 'virginica'),
...     (6.5, 2.8, 4.6, 1.5, 'versicolor'),
...     (6.3, 3.3, 6.0, 2.5, 'virginica'),
...     (6.9, 3.1, 4.9, 1.5, 'versicolor'),
...     (4.6, 3.1, 1.5, 0.2, 'setosa'),
... ]
>>> class Iris(NamedTuple):
...     sl: float
...     sw: float
...     pl: float
...     pw: float
...     species: str
>>> result = starmap(Iris, DATA[1:])
>>> data = list(result)
>>> pprint(data)
[Iris(sl=5.8, sw=2.7, pl=5.1, pw=1.9, species='virginica'),
 Iris(sl=5.1, sw=3.5, pl=1.4, pw=0.2, species='setosa'),
 Iris(sl=5.7, sw=2.8, pl=4.1, pw=1.3, species='versicolor'),
 Iris(sl=6.3, sw=2.9, pl=5.6, pw=1.8, species='virginica'),
 Iris(sl=6.4, sw=3.2, pl=4.5, pw=1.5, species='versicolor'),
 Iris(sl=4.7, sw=3.2, pl=1.3, pw=0.2, species='setosa'),
 Iris(sl=7.0, sw=3.2, pl=4.7, pw=1.4, species='versicolor'),
 Iris(sl=7.6, sw=3.0, pl=6.6, pw=2.1, species='virginica'),
 Iris(sl=4.9, sw=3.0, pl=1.4, pw=0.2, species='setosa'),
 Iris(sl=4.9, sw=2.5, pl=4.5, pw=1.7, species='virginica'),
 Iris(sl=7.1, sw=3.0, pl=5.9, pw=2.1, species='virginica'),
 Iris(sl=4.6, sw=3.4, pl=1.4, pw=0.3, species='setosa'),
 Iris(sl=5.4, sw=3.9, pl=1.7, pw=0.4, species='setosa'),
 Iris(sl=5.7, sw=2.8, pl=4.5, pw=1.3, species='versicolor'),
 Iris(sl=5.0, sw=3.6, pl=1.4, pw=0.3, species='setosa'),
 Iris(sl=5.5, sw=2.3, pl=4.0, pw=1.3, species='versicolor'),
 Iris(sl=6.5, sw=3.0, pl=5.8, pw=2.2, species='virginica'),
 Iris(sl=6.5, sw=2.8, pl=4.6, pw=1.5, species='versicolor'),
 Iris(sl=6.3, sw=3.3, pl=6.0, pw=2.5, species='virginica'),
 Iris(sl=6.9, sw=3.1, pl=4.9, pw=1.5, species='versicolor'),
 Iris(sl=4.6, sw=3.1, pl=1.5, pw=0.2, species='setosa')]
>>> data[0]
Iris(sl=5.8, sw=2.7, pl=5.1, pw=1.9, species='virginica')
>>>
>>> data[0].sl
5.8
>>> data[0].species
'virginica'
>>>
>>> tuple(data[0])
(5.8, 2.7, 5.1, 1.9, 'virginica')

3.8.13. Further Reading

3.8.14. References

3.8.15. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: Typing Annotations NamedTuple
# - Difficulty: easy
# - Lines: 4
# - Minutes: 3

# %% English
# 1. Define `User: NamedTuple` with:
#    - `firstname: str`
#    - `lastname: str`
#    - `age: int`
# 2. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj `User: NamedTuple` z:
#    - `firstname: str`
#    - `lastname: str`
#    - `age: int`
# 2. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 5), \
'Python 3.5+ required'

>>> from typing import get_type_hints
>>> get_type_hints(User)
{'firstname': <class 'str'>, 'lastname': <class 'str'>, 'age': <class 'int'>}

>>> data: User = User(firstname='Mark', lastname='Watney', age=42)
>>> data
User(firstname='Mark', lastname='Watney', age=42)

>>> data: User = User('Mark', 'Watney', 42)
>>> data
User(firstname='Mark', lastname='Watney', age=42)

>>> data: User = ('Mark', 'Watney', 42)
>>> data
('Mark', 'Watney', 42)
"""
from typing import NamedTuple


# Define `User: NamedTuple` with:
# - `firstname: str`
# - `lastname: str`
# - `age: int`
class User:
    ...