Python Patterns: Named Tuples
In Python, sequences are a great way to hold a set of ordered data. As long as the data is simple, a list or tuple is perfect because they are included in every install of Python. But data is not always simple; you can put any object you want in a sequence, making it easy to lose track of what is where.
For example, one might create cards in a virtual address book like this:
card = ( "Alex", "Gude", "email@example.com", None, "17 St., Smaller Town, CA", )
Simple, but a little confusing. What does
None signify? Writing code to work with these objects is error prone:
def check_email(card): """Check if a card has an email address that is valid.""" email = card # 2?! is_valid = email is not None and '@' in email return is_valid
2 the correct index to use? Maybe it was
3? Catching mistakes in the code is tough for anyone reading it.
A dictionary is a natural solution to this problem, because we can use strings as keys, for example
card["email"] instead of
card. But we might need to maintain compatibility with something that expects a sequence, as was the case when passing artists around in my
matplotlib blitting post.
Instead, we could build a class that acts like a list or tuple::
class Card: def __init__(self, first_name, last_name, ...): self.__internal = [first_name, last_name, ...] self.first_name = self.__internal self.last_name = self.__internal ... # etc. def __len__(self): return self.__internal.__len__() def __getitem__(self, key): return self.__internal.__getitem__(key) def __next__(self): return self.__internal.__next__() # and many other methods
Not difficult to write, but tedious due to all of the boilerplate code. Thankfully, someone has already done so.
The named tuple functions exactly like a tuple, with one addition: you can access each component of the tuple by name. Our card example would now looks like this:
from collections import namedtuple Card = namedtuple( "Card", [ "first_name", "last_name", "email", "phone", # Our empty field revealed! "address", ] ) alex_card = Card( "Alex", "Gude", "firstname.lastname@example.org", None, "17 St., Smaller Town, CA", )
This is much cleaner than our original card tuple. We now know the missing value is the phone number! We can access the values with dot operators as well:
card.email. And the named tuple stills works exactly as you would expect for a standard tuple:
# For loops work for item in alex_card: print(item) # We can access with . or  alex_card == alex_card.email # And we can unpack first, last, email, phone, address = alex_tuple
Code that operates on this named tuple is much easier to read as well, because it does not rely on magic numbers:
def new_check_email(card): """Check if a card has an email address that is valid.""" email = card.email is_valid = email is not None and '@' in email return is_valid
Named tuples are not as well known as dictionaries or classes, but they solve a common problem and make your code more readable!