🙏Thoughts & Prayers

The Bare Except That Swallows Everything

TL;DR: Bare except clauses catch everything including KeyboardInterrupt, making code impossible to debug or stop.

#exception-handling#bare-except#debugging#error-suppression

The Code

python
1def process_user_data(user_id):
2    try:
3        user = database.get_user(user_id)
4        user.update_last_login()
5        send_welcome_email(user.email)
6        update_analytics(user)
7        return user
8    except:
9        return None
10

The Prayer 🙏

"Surely nothing will go wrong inside this try block! And if something does go wrong, well... we'll just return None and pretend it never happened. The calling code will figure it out. Right?"

The Reality Check

This bare except: clause catches everything. And when we say everything, we mean EVERYTHING:

  • KeyboardInterrupt when you try to stop the program? Caught.
  • SystemExit when you try to exit gracefully? Caught.
  • MemoryError when you're out of RAM? Caught and ignored.
  • Typo in variable name (user.emal instead of user.email)? Silent failure.
  • Database connection lost? No one will ever know.

Now imagine this code running in production:

  1. Database goes down temporarily
  2. Function silently returns None
  3. Calling code assumes user doesn't exist
  4. Creates duplicate user account
  5. Database comes back up
  6. Now you have two users with the same email
  7. Customer can't log in
  8. Support ticket filed
  9. 3 hours of debugging later, you find this except: block

The real kicker? When you try to debug this with print statements or a debugger, even KeyboardInterrupt (Ctrl+C) gets swallowed. You can't even stop the program gracefully!

The Fix

Option 1: Catch Specific Exceptions (Best Practice)

python
1def process_user_data(user_id):
2    try:
3        user = database.get_user(user_id)
4        user.update_last_login()
5        send_welcome_email(user.email)
6        update_analytics(user)
7        return user
8    except database.UserNotFound:
9        logger.warning(f"User {user_id} not found")
10        return None
11    except database.ConnectionError as e:
12        logger.error(f"Database connection failed: {e}")
13        raise  # Re-raise to let caller handle it
14    except EmailDeliveryError as e:
15        # Email failed, but user update succeeded - that's okay
16        logger.warning(f"Failed to send welcome email: {e}")
17        return user
18

Now you know exactly what can go wrong and handle each case appropriately.

Option 2: Catch Exception (If You Must)

python
1def process_user_data(user_id):
2    try:
3        user = database.get_user(user_id)
4        user.update_last_login()
5        send_welcome_email(user.email)
6        update_analytics(user)
7        return user
8    except Exception as e:
9        # At least log what went wrong!
10        logger.error(f"Failed to process user {user_id}: {e}", exc_info=True)
11        return None
12

Using except Exception: is better than bare except: because it won't catch KeyboardInterrupt, SystemExit, or GeneratorExit - things you almost never want to suppress.

Option 3: Let It Fail (Often the Right Choice)

python
1def process_user_data(user_id):
2    # No try/except at all!
3    user = database.get_user(user_id)
4    user.update_last_login()
5    send_welcome_email(user.email)
6    update_analytics(user)
7    return user
8

Sometimes the best error handling is to let exceptions propagate. The calling code or a framework-level handler can deal with it appropriately.

Lesson Learned

Bare except: clauses catch everything, including KeyboardInterrupt and SystemExit, making your code impossible to debug or stop. Always catch specific exceptions, or at minimum use except Exception: with proper logging.