In Python, there are two philosophies when it comes to accessing items in a dictionary. The first is EAFP (“Easier to Ask Forgiveness than Permission”), which says to access the key and handle a KeyError if one arises. The other is LBYL (“Look Before You Leap”), which says to check for a key’s existence in the dictionary before trying to access it. As with many programming philosophies, different people have different opinions about which approach to use.
This post on StackOverflow that first brought these philosophies to my attention pointed me to the Python glossary entry on EAFP. The Python glossary seems to prefer EAFP in its definition of the term.
In the past, though, I’ve tended to prefer LBYL. The author of Effective Python: 90 Specific Ways to Write Better Python seems also to lean that way in his recommendation, “Prefer get Over in and KeyError to Handle Missing Dictionary Keys.”
I decided on a compromise between the approaches based on a sidebar at RealPython.
My philosophy is to use EAFP when we expect the key to be in the dictionary in most cases but can handle it if the key is absent, and to use LBYL when we don’t expect the key to be in the dictionary in most cases but want to do something if it is present. A recent situation illustrates the philosophy in action.
I have a function that accepts a dictionary as a parameter and uses it to perform some action, e.g.:
def my_function(my_data: Mapping[str, Any]) -> None:
write_to_table(my_data['id'], my_data['name'])
This is an extremely simplified example of a function that uses the dictionary, my_data, to provide arguments to another function.
I want to add some optional data to the dictionary. When present, the function should take additional action based on that data. Most of the time, though, this optional data will not be in the dictionary. This is where I would use the LBYL approach because:
- The key is optional
- The normal case is for the key to be absent
So, I end up with the following:
def my_function(my_data: Mapping[str, Any]) -> None:
write_to_table(my_data['id'], my_data['name'])
other_data = my_data.get('other')
if other_data:
write_to_other_table(other_data)
I have another function that gets some data from a database and cleans it up a little before returning it to the caller, e.g.:
def read_from_table(my_id: int) -> Mapping[str, Any]:
my_data = get_from_db(my_id)
try:
del my_data['id']
except:
pass
return my_data
In this function, there is a key I want to delete from the dictionary. In this case, I expect the key to be present most (actually, all) of the time, but I can keep going even if the key is absent (since I want to delete the key anyway). I use the EAFP approach here because:
- The key is optional
- The normal case is for the key to be present
In summary, when it comes to choosing between EAFP or LBYL, my philosophy is to use the approach that best expresses my expectation for what is the normal case, i.e. what I expect to be true most often:
- Use EAFP when you expect the key to be present most of the time
- Use LBYL when you expect the key to be absent most of the time
I believe this makes it explicit to a reader what I expect the usual case to be, and being explicit in my intent is Pythonic.