Syllabus Lesson 59 of 239 · Pythonic Code, Testing & Capstone
Pythonic Code, Testing & Capstone

EAFP, Truthiness, and the Mutable Default Trap

This lesson collects three idioms that separate beginner code from mid-tier code. The last one is a real bug that bites almost everyone once.

EAFP vs LBYL

Python prefers EAFP: Easier to Ask Forgiveness than Permission. Instead of checking first (Look Before You Leap), you try the operation and catch the failure:

# LBYL (more brittle)
if "age" in data:
    age = data["age"]
else:
    age = 0

# EAFP (pythonic)
try:
    age = data["age"]
except KeyError:
    age = 0

# or just
age = data.get("age", 0)

Truthiness

Empty containers, 0, "", and None are all falsy. So you rarely compare to them explicitly:

# instead of: if len(items) > 0:
if items:
    print("has items")

# instead of: if name != "":
if name:
    print("has a name")

The mutable default argument trap

A default argument is evaluated once, when the function is defined, not each time it is called. So a mutable default like [] is shared across every call. Watch this go wrong:

# BUG: the list persists between calls
def add_item(item, bucket=[]):
    bucket.append(item)
    return bucket

add_item("a")  # ['a']
add_item("b")  # ['a', 'b']  -- not what you wanted!

The fix is the standard None sentinel pattern: default to None, then create a fresh list inside:

def add_item(item, bucket=None):
    if bucket is None:
        bucket = []
    bucket.append(item)
    return bucket

Now each call with no bucket starts fresh.

Your turn

Fix the trap. Write add_item(item, bucket=None) that appends item to bucket and returns it, but creates a brand new list when no bucket is passed, so separate calls do not share state. Use the if bucket is None: pattern.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output