LBYL came more naturally to me in my early years of programming. It seemed to have fewer obstacles in those early stages, fewer tricky concepts.
And in my 10+ years of teaching Python, I also preferred teaching LBYL to beginners and delaying EAFP until later.
But over the years, as I came to understand Python’s psyche better, I gradually shifted my programming style—and then, my teaching style, too.
So, what are LBYL and EAFP? And which one is more suited to Python?
I’m running a series of three live workshops starting next week.
Each workshop is 2 hours long, so plenty to time to explore core Python topics:
#1 • Python’s Plumbing: Dunder Methods and Python’s Hidden Interface
#2 • Pythonic Iteration: Iterables, Iterators, itertools#3 • To Inherit or Not? Inheritance, Composition, Abstract Base Classes, and Protocols
Read more and book your place here:
https://www.thepythoncodingstack.com/p/when-it-works-is-not-good-enough
Look Both Sides Before Crossing the Road
You should definitely look before you leap across a busy road…or any road, really. And programming also has a Look Before You Leap concept—that’s LBYL—when handling potential failure points in your code.
Let’s start by considering this basic example. You define a function that accepts a value and a list. The function adds the value to the list if the value is above a user-supplied threshold:
def add_value_above_threshold(value, threshold, data):
if value >= threshold:
data.append(value)You can confirm this short code works as intended:
# ...
prices = []
add_value_above_threshold(12, 5, prices)
add_value_above_threshold(3, 5, prices)
add_value_above_threshold(9, 5, prices)
print(prices)This code outputs the list with the two prices above the threshold:
[12, 9]However, you want to ensure this can’t happen:
# ...
products = {}
add_value_above_threshold(12, 5, products)Now, products is a dictionary, but add_value_above_threshold() was designed to work with lists and not dictionaries:
Traceback (most recent call last):
...
add_value_above_threshold(12, 5, products)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
...
data.append(value)
^^^^^^^^^^^
AttributeError: ‘dict’ object has no attribute ‘append’One option is the look before you leap (LBYL):
def add_value_above_threshold(value, threshold, data):
if not isinstance(data, list):
print(”Invalid format. ‘data’ must be a list”)
return
if value >= threshold:
data.append(value)Now, the function prints a warning when you pass a dictionary, and it doesn’t crash the program!
But this is too restrictive.
Let’s assume you decide to use a deque instead of a list:
from collections import deque
# ...
prices = deque()
add_value_above_threshold(12, 5, prices)
add_value_above_threshold(3, 5, prices)
add_value_above_threshold(9, 5, prices)
print(prices)This code still complains that it wants a list and doesn’t play ball:
Invalid format. ‘data’ must be a list
Invalid format. ‘data’ must be a list
Invalid format. ‘data’ must be a list
deque([])But there’s no reason why this code shouldn’t work since deque also has an .append() method.
You could change the call to isinstance() to include the deque data type—isinstance(data, list | deque)—but then there may be other data structures that are valid and can be used in this function. You don’t want to have to write them all.
If you’re well-versed with the categories of data structures—perhaps because you devoured the The Python Data Structure Categories Series—then you might conclude you need to check whether the object is a MutableSequence since all mutable sequences have an .append() method. You can import MutableSequence from collections.abc and use isinstance(data, MutableSequence). Now you’re fine to use lists, deques, or any other mutable sequence.
This version fits better with Python’s duck-typing philosophy. It doesn’t restrict the function to a limited number of data types but to a category of data types. This category is defined by what the data types can do. In duck typing, you care about what an object can do rather than what it is. You can read more about duck typing in Python in this post: When a Duck Calls Out • On Duck Typing and Callables in Python
However, you could still have other data types that have an .append() method but may not fully fit into the MutableSequence category. There’s no reason you should exclude those data types from working with your function.
Perhaps, you could use Python’s built-in hasattr() to check whether the object you pass has an .append() attribute. You’re now checking whether the object has the required attribute rather than what the object is.
But if you’re going through all this trouble, you can go a step further.
Just Go For It and See What Happens
Why not just run the line of code that includes data.append() and see what happens? Ah, but you don’t want the code to fail if you use the wrong data type—you only want to print a warning, say.
That’s where the try..except construct comes in:
def add_value_above_threshold(value, threshold, data):
if value < threshold: # inequality flipped to avoid nesting
return
try:
data.append(value)
except AttributeError:
print(
“Provided data structure does not support appending values.”
)This is the Easier to Ask for Forgiveness than Permission (EAFP) philosophy. Just try the code. If it doesn’t work, you can then deal with it in the except block. Now, this fits even more nicely with Python’s duck typing philosophy. You’re asking the program whether data can append a value. It doesn’t matter what data is–can it append a value?
You don’t have to think about all the valid data types or which category they fall into. And rather than checking whether the data type has the .append() attribute first, you just try to run the code and deal with the consequences later. That’s why it’s easier to ask for forgiveness than permission.
But don’t use this philosophy when crossing a busy road. Stick with “look before you leap” there!



