Two ways to handle a missing dict key:
# LBYL — Look Before You Leap
if "value" in data:
n = data["value"]
else:
n = 0
# EAFP — Easier to Ask Forgiveness than Permission
try:
n = data["value"]
except KeyError:
n = 0They look the same. What's the difference?
In intent, very small. In Python culture, EAFP is preferred. The reasons:
if "value" in data check and the actual data["value"] lookup, another thread might delete the key. EAFP doesn't have that gap — you just try the operation and handle the error if it happens.try with no exception is cheap; except only matters when something fails. The if check happens every call, including the success cases.if hasattr(obj, "x"): obj.x only handles missing attributes. try: obj.x; except AttributeError: ... covers attribute errors plus anything else that goes wrong inside obj.x's descriptor (week 4 territory).Are there cases where LBYL is better?
Yes — when the check is cheaper than the operation, or when the operation has side effects you don't want to run twice. Checking if file_exists("x.txt") before opening is fine when the alternative is opening, catching, closing partial state. Both styles have their place.
And dict.get(key, default)?
Best version of the dict case. Neither EAFP nor LBYL — a method designed for the "give me this or a default" pattern:
n = data.get("value", 0) # cleanestUse .get() when it fits. Use EAFP when there's no method that fits. Use LBYL only when the check is cheaper or when the operation has side effects.
EAFP — Easier to Ask Forgiveness than Permission. Just try the operation and handle the error.
LBYL — Look Before You Leap. Check first, act second.
# LBYL
if "value" in data:
n = data["value"]
else:
n = 0
# EAFP
try:
n = data["value"]
except KeyError:
n = 0Race conditions. Another thread (or process, or external system) might change the state between the check and the use:
# LBYL — racy
if os.path.exists("f.txt"):
open("f.txt") # could fail if file deleted in between
# EAFP — atomic
try:
open("f.txt")
except FileNotFoundError:
...Duck typing. EAFP handles "any object that supports this operation" without needing isinstance checks:
# EAFP — works for any sized container
try:
n = len(thing)
except TypeError:
n = 0
# LBYL — would need isinstance(thing, list) or isinstance(thing, str) or ...Performance. When success is the common case, the try has zero overhead until something fails — except only runs on the rare error path. LBYL pays the check cost on every call, success or not.
The check is genuinely cheap and the operation has side effects:
# LBYL — better, the operation can't be cleanly retried
if user.is_authenticated:
transfer_money(user)
# EAFP — bad, might transfer half the money before failing
try:
transfer_money(user)
except NotAuthenticatedError:
...When the standard library provides a method for the pattern, use it:
# Not bad: try / except KeyError
n = data.get("value", 0)
# Not bad: try / except AttributeError
n = getattr(obj, "x", 0)
# Not bad: try / except IndexError
n = items[0] if items else None# Not idiomatic
if len(items) > 0:
...
if items != []:
...
# Idiomatic
if items:
...Empty containers are falsy. Use that — readers expect it.
is None for None checks# Not idiomatic
if x == None:
...
# Idiomatic
if x is None:
...is checks identity. There's exactly one None object in the runtime, so x is None is the right check.
# Not idiomatic
if flag == True:
...
# Idiomatic
if flag:
...Given a dict, refactor a if key in d: ... else: ... block into a try/except KeyError block. Print which version is preferred in idiomatic Python.
Two ways to handle a missing dict key:
# LBYL — Look Before You Leap
if "value" in data:
n = data["value"]
else:
n = 0
# EAFP — Easier to Ask Forgiveness than Permission
try:
n = data["value"]
except KeyError:
n = 0They look the same. What's the difference?
In intent, very small. In Python culture, EAFP is preferred. The reasons:
if "value" in data check and the actual data["value"] lookup, another thread might delete the key. EAFP doesn't have that gap — you just try the operation and handle the error if it happens.try with no exception is cheap; except only matters when something fails. The if check happens every call, including the success cases.if hasattr(obj, "x"): obj.x only handles missing attributes. try: obj.x; except AttributeError: ... covers attribute errors plus anything else that goes wrong inside obj.x's descriptor (week 4 territory).Are there cases where LBYL is better?
Yes — when the check is cheaper than the operation, or when the operation has side effects you don't want to run twice. Checking if file_exists("x.txt") before opening is fine when the alternative is opening, catching, closing partial state. Both styles have their place.
And dict.get(key, default)?
Best version of the dict case. Neither EAFP nor LBYL — a method designed for the "give me this or a default" pattern:
n = data.get("value", 0) # cleanestUse .get() when it fits. Use EAFP when there's no method that fits. Use LBYL only when the check is cheaper or when the operation has side effects.
EAFP — Easier to Ask Forgiveness than Permission. Just try the operation and handle the error.
LBYL — Look Before You Leap. Check first, act second.
# LBYL
if "value" in data:
n = data["value"]
else:
n = 0
# EAFP
try:
n = data["value"]
except KeyError:
n = 0Race conditions. Another thread (or process, or external system) might change the state between the check and the use:
# LBYL — racy
if os.path.exists("f.txt"):
open("f.txt") # could fail if file deleted in between
# EAFP — atomic
try:
open("f.txt")
except FileNotFoundError:
...Duck typing. EAFP handles "any object that supports this operation" without needing isinstance checks:
# EAFP — works for any sized container
try:
n = len(thing)
except TypeError:
n = 0
# LBYL — would need isinstance(thing, list) or isinstance(thing, str) or ...Performance. When success is the common case, the try has zero overhead until something fails — except only runs on the rare error path. LBYL pays the check cost on every call, success or not.
The check is genuinely cheap and the operation has side effects:
# LBYL — better, the operation can't be cleanly retried
if user.is_authenticated:
transfer_money(user)
# EAFP — bad, might transfer half the money before failing
try:
transfer_money(user)
except NotAuthenticatedError:
...When the standard library provides a method for the pattern, use it:
# Not bad: try / except KeyError
n = data.get("value", 0)
# Not bad: try / except AttributeError
n = getattr(obj, "x", 0)
# Not bad: try / except IndexError
n = items[0] if items else None# Not idiomatic
if len(items) > 0:
...
if items != []:
...
# Idiomatic
if items:
...Empty containers are falsy. Use that — readers expect it.
is None for None checks# Not idiomatic
if x == None:
...
# Idiomatic
if x is None:
...is checks identity. There's exactly one None object in the runtime, so x is None is the right check.
# Not idiomatic
if flag == True:
...
# Idiomatic
if flag:
...Given a dict, refactor a if key in d: ... else: ... block into a try/except KeyError block. Print which version is preferred in idiomatic Python.
Create a free account to get started. Paid plans unlock all tracks.