Here are some ways you can write a lookup in Python. Not all of these patterns are obviously a lookup, but all of them can be translated to one another, so it's worth to know these patterns. That way you can make a conscious decision to choose a specific form. Thanks to Ori for inspiring this post. Our talk about state machines was enlightening!
Classic.
# main.py name_to_age = {'Alice': 37, 'Bob': 21, 'Charlie': 59} print(name_to_age['Alice']) # Raise KeyError if Alice not found print(name_to_age.get('Alice', 0)) # Print 0 if Alice not found
Every dictionary corresponds to an if-else statement and vice-versa. Rewriting between the two can sometimes come in handy. The if-else is a lot more explicit, which makes it easier to debug, but it's also a lot more verbose. Additionally, a dictionary lookup cannot be anything but a lookup, whereas the if-else is more general and can do pretty much anything it wants, including deleting your system32 or /bin/.
# main.py def name_to_age(name: str) -> int: if name == 'Alice': return 37 elif name == 'Bob': return 21 elif name == 'Charlie': return 59 else: # return 0 if you want to reproduce name_to_age.get behaviour raise NameError(f'{name=} not found!') print(name_to_age('Alice'))
Sometimes data is in separate lists (or series, or whatever). This is a simple lookup pattern, just make sure you give the index a good name.
# main.py names = ['Alice', 'Bob', 'Charlie'] ages = [37, 21, 59] i_name = names.index('Alice') print(ages[i_name])
If you find yourself doing this a lot with the same data, consider making a helper so you don't need to deal with indexes.
name_to_age = dict(zip(names, ages)) print(name_to_age('Alice'))
This scenario is also relevant when you have a list of objects. Like Person
with attributes name
and age
.
# main.py names_and_ages = [('Alice', 37), ('Bob', 21), ('Charlie', 59)] alice_matches = [age for name, age in names_and_ages if name == 'Alice'] if len(alice_matches) != 1: raise ValueError(f'Unexpected number of matches for Alice, got {alice_matches}') print(alice_matches[0])
Here you can also quickly make a helper.
name_to_age = {name: age for name, age in names_and_ages} # verbose but explicit # name_to_age = dict(names_and_ages) # short print(name_to_age['Alice'])
Here, the age is encoded by the index of the name in the list. It's a little weird, but this is how you might encode a game of life grid, for instance.
# main.py name_by_age = [None for _ in range(21)] + ['Bob'] + [None for _ in range(37-21-1)] + ['Alice'] + [None for _ in range(59-21-2)] + ['Charlie'] print(name_by_age.index('Alice'))
Flipping data will also work here. Maybe filter out the None
s.
name_to_age = {name: i for i, name in enumerate(name_by_age) if name is not None} print(name_to_age['Alice'])
Imports always do a lookup from name to code. Not seen often, but not too far out there in my opinion.
# alice.py age = 37 # bob.py age = 21 # charlie.py age = 59 # main.py from alice import age print(age)
Alternatively you might have something like this.
# ages.py alice_age = 37 bob_age = 21 charlie_age = 59 # main.py from ages import alice_age print(alice_age)
This is often useful in Advent of Code if you have some kind of 2d grid. The most intuitive data structure is a list of lists or a Numpy array.
grid = [[1 if x % 2 == 0 and y % 7 == 0 else 0 for x in range(20)] for y in range(20)] print(grid[7][8])
But this might not be the best solution. Doing things in this array means looking up a coordinate pair, so we can also use a dictionary.
grid = {(x, y): 1 if x % 2 == 0 and y % 7 == 0 else 0 for x in range(20) for y in range(20)} print(grid[7, 8])
Sometimes you may write something like this.
name_to_age = {'Alice': 37, 'Bob': 21, 'Charlie': 59} if 'Dilbert' in name_to_age: dilbert_age = name_to_age['Dilbert'] else: dilbert_age = 88
Already mentioned up above, you can use get
to provide a default argument instead.
name_to_age = {'Alice': 37, 'Bob': 21, 'Charlie': 59} print(name_to_age.get('Dilbert', 88))
defaultdict
and invertingSay you need to know who is 37.
name_to_age = {'Alice': 37, 'Bob': 21, 'Charlie': 59} age_to_name = {v: k for k, v in in name_to_age.items()} print(age_to_name[37])
This will only work if your values are unique. If Dilbert is also 37, you are not going to find Alice in age_to_name
. The solution is to keep a list of keys that map to this value, like so.
name_to_age = {'Alice': 37, 'Bob': 21, 'Charlie': 59, 'Dilbert': 37} age_to_name = {v: [] for v in name_to_age.values()} for name, age in name_to_age.items(): age_to_name[age].append(name) print(age_to_name[37])
Use defaultdict
for this, so you don't need to loop over your collection twice. A defaultdict
puts a value provided by the function you give it into the dictionary if it hasn't seen the key before.
from collections import defaultdict name_to_age = {'Alice': 37, 'Bob': 21, 'Charlie': 59, 'Dilbert': 37} age_to_name = defaultdict(list) for name, age in name_to_age.items(): age_to_name[age].append(name) print(age_to_name[37])home