Named tuples
Named tuples
Named tuples are like tuples in that they are immutable. However, unlike plain-vanilla tuples, named tuples allow us to give names to the elements they contain. These elements are called fields. By giving names to fields we can refer to them by their name, as well as by their index. This can make our code easier to read and to reason about.
To define a named tuple we need to import from the collections
module. The collections
module is full of useful varieties of collections. Collections are container datatypes—objects that contain other objects. It’s true that all sequences, including tuples, are containers of a sort, but the ones in the collections
module have enhanced functionality for special purposes.
Let’s see how named tuples work with some practical examples. We’ve seen coordinates including latitude and longitude, so let’s continue along those lines. Let’s define a special-purpose namedtuple
type for coordinates. It makes sense that the fields will be latitude and longitude, and we’ll give these the names “lat” and “lon”, respectively.
>>> from collections import namedtuple
>>> Coordinates = namedtuple('Coordinates', ['lat', 'lon'])
Now we’ve defined a new type of object. We’ve seen many types of object before: int
, list
, range
, etc. This code (above) defines a new class: Coordinates
. We can use this new class to create objects of type Coordinates
. How? Well, when we defined this new class, Python automatically equipped it with some features including its own constructor. We’ll use this constructor to instantiate new objects, but first we need some data.
Here are some coordinates for a few US state capital cities.
City | Latitude | Longitude |
---|---|---|
Montpelier, VT | 44.2601° N | 72.5754° W |
Concord, NH | 43.2201° N | 71.5491° W |
Augusta, ME | 44.3315° N | 69.7890° W |
Albany, NY | 42.6526° N | 73.7562° W |
Boston, MA | 42.3611° N | 71.0571° W |
When we render west longitudes as floats we use negative numbers (positive values are for longitudes east of the prime meridian). Let’s use these data to create some objects.
>>> montpelier = Coordinates(44.2601, -72.5754)
>>> concord = Coordinates(43.2201, -71.5491)
>>> augusta = Coordinates(44.3315, -69.7890)
>>> albany = Coordinates(42.6526, -73.7562)
>>> boston = Coordinates(42.3611, -71.0571)
Let’s unpack this. When we defined the new Coordinates
class, we specified that these objects would contain two fields, lat
and lon
. That’s what we did here:
>>> Coordinates = namedtuple('Coordinates', ['lat', 'lon'])
Then to create instances of our new class, we had to pass the required arguments to the Coordinates
constructor. That’s what we did here:
>>> montpelier = Coordinates(44.2601, -72.5754)
The first argument passed to the constructor was assigned to the field lat
and the second argument was assigned to lon
. We can check this by printing these objects:
>>> print(montpelier)
=44.2601, lon=-72.5754) Coordinates(lat
This is telling us we have an object of type Coordinates
where the lat
field has the value 44.2601
and the lon
field has the value -72.5754
. What’s great about named tuples is now we can refer to latitude and longitude by name!
>>> print("The coordinates of Boston are "
... f"{boston.lat}° N, {-boston.lon}° W.")
The coordinates of Boston are 42.3611° N, 71.0571° W.
Pretty cool, huh?
Named tuples have another benefit. When we define a named tuple, we specify exactly those fields an object may contain, and this is enforced. If we used a plain tuple, nothing would prevent us from creating a tuple with too many or too few elements.
>>> sacramento = Coordinates(38.5781)
Traceback (most recent call last):
File "<python-input-8>", line 1, in <module>
sacramento = Coordinates(38.5781)
TypeError: Coordinates.__new__() missing 1
required positional argument: 'lon'
So we can’t create a Coordinates
object without the necessary value for longitude. This also fails (trying to add elevation):
>>> sacramento = Coordinates(38.5781, -121.4944, 8.1)
Traceback (most recent call last):
File "<python-input-9>", line 1, in <module>
sacramento = Coordinates(38.5781, -121.4944, 8.1)
TypeError: Coordinates.__new__() takes 3 positional
arguments but 4 were given
Now, this error message might seem to be incorrect. When we construct objects of this type there’s another argument which precedes the ones we supply, but we’ll ignore that for now, and if you ever learn object-oriented programming you’ll know why. For now, just trust that this is indeed correct.
So named tuples allow us to refer to fields by name rather than by index, and they have the added benefit of enforcing a particular structure for our data: a Coordinates
object must have exactly two fields, the first being the latitude, the second being longitude. This kind of constraint helps prevent many defects in programming.
Let’s expand on this and create a new named tuple type which generalizes a bit, including a field for location name (location needn’t be a city) and elevation, as well as latitude and longitude.
>>> Location = namedtuple('Location', ['name', 'lat',
... 'lon', 'elevation'])
Now let’s instantiate some new objects:
>>> montpelier = Location('Montpelier', 44.2601, -72.5754, 147.8)
>>> concord = Location('Concord', 43.2201, -71.5491, 88.4)
>>> augusta = Location('Augusta', 44.3315, -69.7890, 13.7)
>>> albany = Location('Albany', 42.6526, -73.7562, 6.1)
>>> boston = Location('Boston', 42.3611, -71.0571, 6.4)
Now, let’s create a function which computes the great circle distance between locations.
from collections import namedtuple
import math
= namedtuple('Location', ['name', 'lat',
Location 'lon', 'elevation'])
def distance(c1, c2):
"""Use haversine formula to compute great circle
distance in km between two locations, c1 and c2. """
= 6_371.2 # km
EARTH_RADIUS = math.radians(c1.lat)
lat1 = math.radians(c1.lon)
lon1 = math.radians(c2.lat)
lat2 = math.radians(c2.lon)
lon2 = lat2 - lat1
d_lat = lon2 - lon1
d_lon = (math.sin(d_lat / 2) ** 2
a + math.cos(lat1) * math.cos(lat2)
* math.sin(d_lon / 2) ** 2)
= 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
c return c * EARTH_RADIUS
This uses the haversine formula. If you’re curious about how it works see: https://en.wikipedia.org/wiki/Haversine_formula. But for now, just see how we’ve used named tuples to implement this. It’s easy to get confused with indices (which comes first, latitude or longitude?). But with named fields, we can’t make that mistake (another benefit that helps reduce the likelihood of introducing bugs into our code).
>>> distance(concord, montpelier)
142.0268062952325
That’s in kilometers, so that’s about 89 miles. Looks correct. Now let’s test distance to Sacramento, CA.
>>> sacramento = Location('Sacramento', 38.5781, -121.4944, 8.1)
>>> distance(sacramento, montpelier)
4066.6463034784333
4,067 kilometers is about 2,527 miles. Yup. That checks out too.
Matters of style and program structure
I know. You’re thinking “What’s up with the identifier Location
and why do we define this before our function definitions?” Great question. For most of this book we haven’t worried about classes (new types of objects we can define ourselves), but with named tuples we now have classes. Where do they fit in the program structure? Right between constants (if any) and function definitions (if any). Also, we use InitialCaps format for naming classes, hence Coordinates
and Location
.
Trying to access a non-existent field
If you try to access a field within a named tuple that does not exist, you’ll get an AttributeError
.
>>> from collections import namedtuple
>>> Xyz = namedtuple('Xyz', ['x', 'y', 'z'])
>>> point = Xyz(5.2, 3.9, 2.2)
>>> point.q # there is no field `q`
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
point.q
AttributeError: 'Xyz' object has no attribute 'q'
When should I use named tuples?
Whenever you have multiple tuples that hold the same kinds of data, you should consider using named tuples. For a single object it’s not worth the trouble, but if you have multiple instances and you want to enforce the number and order of fields they contain, and you want to refer to fields by name, you should consider named tuples. Some use cases:
- Any objects with coordinates, for example, latitude and longitude; or x, y, and z coordinates in three-dimensional space, etc.
- Read-only records from a database or CSV file.
- MIDI messages / events in a music system (“MIDI” is a standard for musical instrument digital interface). MIDI messages include note-on, note-off, and other controls. For example, a note-on message would include the pitch, velocity, and MIDI channel for a given musical note (MIDI supports multiple channels for multiple instruments).
- Abundant applications in physics and engineering, e.g.,
- resistors with fields for resistance, tolerance, power rating, temperature coefficient of resistance (TCR), or other parameters;
- particles with mass, charge, and spin;
- materials with specific properties like density, ductility, elasticity, or tensile strength.
Benefits of named tuples
- well-defined fields
- field number enforced
- immutability
- improved readability with names
- lighter weight than full-scale object-oriented programming
- can help reduce likelihood of defects
Comprehension check
Let’s say we were reading records from a database (don’t worry about how we do that just yet), and we wanted to store read-only data about students as named tuples. How would you define a named tuple to represent a student record with fields: given name, surname, date of birth, home state, and university email address?
Assuming we have a valid definition of a
Student
named tuple class, how would we instantiate an object for our old acquaintance Egbert Porcupine? Let’s say Egbert’s birthday is 1960-01-01 (January 1 1960), his home state is MI (Michigan), and his email address is eporcupi@uvm.edu.Consider the advantages and disadvantages of using a dictionary or a named tuple to represent a student record. Under what conditions would a dictionary be a more appropriate choice? Under what conditions would a named tuple be the better choice?
Copyright © 2023–2025 Clayton Cafiero
No generative AI was used in producing this material. This was written the old-fashioned way.