183

What is meant by "using the EAFP principle" in Python? Could you provide any examples?

Braiam
  • 1
  • 11
  • 47
  • 78
Unpaid Oracles
  • 2,047
  • 2
  • 14
  • 8

4 Answers4

277

From the glossary:

Easier to ask for forgiveness than permission. This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. This clean and fast style is characterized by the presence of many try and except statements. The technique contrasts with the LBYL style common to many other languages such as C.

An example would be an attempt to access a dictionary key.

EAFP:

try:
    x = my_dict["key"]
except KeyError:
    # handle missing key

LBYL:

if "key" in my_dict:
    x = my_dict["key"]
else:
    # handle missing key

The LBYL version has to search the key inside the dictionary twice, and might also be considered slightly less readable.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 51
    An enhancement would be that another advantage is the avoidance of race conditions... eg, just try opening a file and if you get it, you got it. Instead of seeing *if you can get it*, then trying to get it afterwards and realise that in the miniscule amount of time between the check and access attemp, you can longer get it. – Jon Clements Jul 06 '12 at 12:17
  • 34
    Python also provides for a way of avoiding both of those, if the handler is just assigning a default value to `x` when the key doesn't exist: `x = mydict.get('key')` will return `None` if `'key'` is not in `my_dict`; you could also do `.get('key', )`, and then x will be assigned that something if the key isn't in the dictionary. `dict.setdefault()` and `collections.defaultdict` are nice things for avoiding excess code as well. – JAB Jul 13 '12 at 17:29
  • 2
    I think `except KeyError` as well as `AttributeError` are simple but some of the worst examples. So many times I was stuck debugging something because `except AttributeError` was put in wrong place, which end up catching wrong attribute error raised deeper down in the chain. Better examples I think are: `try: open() ... except: IOError`. Or `try: parseLine() ... except ParseError` – Ski May 23 '17 at 11:10
  • 5
    @ski That's a slightly different problem. You should _always_ keep the try block as minimal as possible to avoid catching the wrong exception. Also note that I don't generally prefer the EAFP style. I'm just answering the question here, and state that some people prefer it. I deicde on a case-by-case basis what code looks the most readable to me. – Sven Marnach May 23 '17 at 13:01
  • 3
    I thought it'd be worth mentioning that [Grace Hopper](https://en.wikipedia.org/wiki/Grace_Hopper) is likely the source for this phrase, with her quote: "Dare and Do. It's easier to ask forgiveness than it is to get permission" (not restricted to programming). – Fabien Snauwaert May 26 '17 at 10:36
  • I'd add that it's considered bad practice to catch exceptions that are too broad. It's enticing to simply include `except Exception` to catch any problem that might occur, but it's much better to catch errors explicitly. (Note that this example includes an explicit exception statement.) – DaveL17 Apr 21 '23 at 12:13
29

I'll try to explain it with another example.

Here we're trying to access the file and print the contents in console.

LBYL - Look Before You Leap :

We might want to check if we can access the file and if we can, we'll open it and print the contents. If we can't access the file we'll hit the else part. The reason that this is a race condition is because we first make an access-check. By the time we reach with open(my_file) as f: maybe we can't access it anymore due to some permission issues (for example another process gains an exclusive file lock). This code will likely throw an error and we won't be able to catch that error because we thought that we could access the file.

import os

my_file = "/path/to/my/file.txt"

# Race condition
if os.access(my_file, os.R_OK):
    with open(my_file) as f:
        print(f.read())
else:
    print("File can't be accessed")

EAFP - Easier to Ask for Forgiveness than Permission :

In this example, we're just trying to open the file and if we can't open it, it'll throw an IOError. If we can, we'll open the file and print the contents. So instead of asking something we're trying to do it. If it works, great! If it doesn't we catch the error and handle it.

# # No race condition
try:
    f = open(my_file)
except IOError as e:
    print("File can't be accessed")
else:
    with f:
        print(f.read())
ds4940
  • 3,382
  • 1
  • 13
  • 20
Apoorv Patne
  • 679
  • 7
  • 24
  • I'm not sure it's correct to describe this as a race condition. Either the file is accessible or not. – ds4940 May 08 '19 at 09:17
  • 5
    @ds4940 It is the race condition if file accessibility changes between lines 6 and 7, that is between checking if the file is accessible and opening it. – Markus von Broady May 10 '19 at 13:42
  • @MarkusvonBroady agreed, edited the answer to provide an example of the other participant in the race condition. – ds4940 May 10 '19 at 14:35
  • I've always assumed LBYL is the preferred way to do things instead of `try, except` blocks, correct? – SurpriseDog Jul 03 '21 at 18:47
  • 1
    @SurpriseDog maybe in other languages but not in Python. Python expects you to use exceptions, so it has been optimized to be efficient when an exception is not thrown. Exceptions improve readability because the error handling code is grouped together after the working code, and it reduces the amount of indenting when every possible error needs to be handled in-line. – Mark Ransom Oct 20 '21 at 03:35
  • @SurpriseDog P.S. the example given for exceptions in this answer isn't optimal, because the `else` block is completely unnecessary - that code should have been part of the `try` block instead. – Mark Ransom Oct 20 '21 at 03:39
10

I call it "optimistic programming". The idea is that most times people will do the right thing, and errors should be few. So code first for the "right thing" to happen, and then catch the errors if they don't.

My feeling is that if a user is going to be making mistakes, they should be the one to suffer the time consequences. People who use the tool the right way are sped through.

Engineer
  • 834
  • 1
  • 13
  • 27
0

To add @sven-marnach to answer,

You could use:

dict[“key”] = dict.get(“key”, None)

This is better than the search twice issue he mentions for doing LBYL.

abgup
  • 42
  • 6