Errors happen in every program: a file is missing, a user types “ten” instead of 10, a network connection drops. Python’s try and except keywords let you catch those errors and decide what happens next, instead of letting your program crash. This page starts with a short answer and then goes through everything else you need: catching specific and multiple exceptions, else and finally, raising your own exceptions, custom exceptions, exception groups, and the best practices that keep your error handling from hiding bugs.
What are try and except in Python?
try and except are Python’s way of handling errors. You put code that might fail in a try block. If an error (an exception) happens there, Python stops running that block and jumps to the matching except block, where you deal with the problem. If nothing goes wrong, the except block is skipped.
Here’s a typical example. Converting text to a number fails when the text isn’t a number, so we catch the ValueError that int() raises:
text = "42a"
try:
number = int(text)
print(f"You entered {number}")
except ValueError:
print(f"{text!r} is not a whole number")Code language: Python (python)
Output:
'42a' is not a whole number
Change text to "42" and you’ll see You entered 42 instead. Without the try and except, the same bad input stops the whole program with a traceback:
The last line of a traceback tells you which exception happened (ValueError), and that’s the name you put after except. If error messages like this still feel intimidating, the free lesson Errors Are Part of Programming in my Python Fundamentals course lets you cause one on purpose and fix it, right in your browser.
Try catch in Python? It’s called try except
If you come from Java, JavaScript, C# or PHP, you’re probably looking for try ... catch. Python has no catch keyword. It uses except instead, and it works the same way. Here’s how the keywords map:
| Java / JavaScript / C# | Python |
|---|---|
try { ... } | try: |
catch (IOException e) { ... } | except OSError as e: |
finally { ... } | finally: |
throw new Exception("...") | raise Exception("...") |
| (no equivalent) | else: runs only if no exception happened |
So a Java-style try catch looks like this in Python:
try:
print(2/0)
except ZeroDivisionError as e:
print("Caught:", e)
finally:
print("This always runs")Code language: Python (python)
Caught: division by zero This always runs
A few other differences are worth knowing. Python has no checked exceptions, so you never declare which exceptions a function throws. Raising an exception is called raising, not throwing (though people use both words). And exceptions are cheap enough in Python that using them for normal control flow is common and accepted. More on that in It’s better to ask for forgiveness.
What is an exception?
An exception is a condition that arises during a program’s execution. It is a signal that something unexpected happened. Python represents exceptions by an object of a specific type.
In Python, all built-in, non-system-exiting exceptions are derived from the Exception class. Exceptions have their own descriptive names. For example, if you try to divide a number by zero, you will get a ZeroDivisionError exception, which is also a subclass of the Exception class.
For a complete hierarchy of all exceptions, you can view the Python manual if you’re interested. Here’s a small excerpt from this hierarchy to illustrate:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- BaseExceptionGroup
+-- Exception
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- OSError
| +-- FileNotFoundError
| +-- PermissionError
.....
This hierarchy matters when you catch exceptions: except ArithmeticError also catches a ZeroDivisionError, and except OSError also catches a FileNotFoundError. If your knowledge about objects, classes, and inheritance is a bit rusty, you may want to read my article on objects and classes and my article on inheritance first.
Python try except
When something unexpected occurs, we can raise an exception at the point of the error. When an exception is raised, Python stops the current flow of execution and starts looking for an exception handler that can handle it. So what is an exception handler? Here’s where the try and except statements come into play.

As the illustration shows, we start a code block with a try statement. This means: try to run this code, but an exception might occur.
After our try block, one or more except blocks follow (or a finally block, more on that later). These except blocks can catch an exception, as we usually call this. Each except block can handle a specific type of exception.
Remember: classes are hierarchical. For that reason, exceptions are hierarchical as well. Python checks the except blocks from top to bottom and runs the first one that matches, so they must go from the most specific, like a ZeroDivisionError, to less specific, like an ArithmeticError. Get the order wrong and the specific block never runs:
try:
print(2/0)
except ArithmeticError:
print("Some arithmetic went wrong")
except ZeroDivisionError:
print("You can't divide by zero!")Code language: Python (python)
Some arithmetic went wrong
The same thing happens, on a larger scale, if you start with an except block that catches Exception. That first block catches almost everything, so the other except blocks are useless.
When an exception is raised, the handler that catches it can be nearby, but it can also be in a different function or even a different module. Python doesn’t scan your code randomly for an exception handler. Instead, the handler should be somewhere in the call stack.
Call stack
A call stack is an ordered list of functions that are currently being executed. For example, you might call function A, which calls function B, which calls function C. We now have a call stack consisting of A, B, and C. When C raises an exception, Python will look for an exception handler in this call stack, going backward from end to start. It can be in function C (closest to the exception), in function B (somewhat farther), in function A, or even at the top level of the program where we called function A.
If Python finds a suitable except block, it executes the code in that block. If it doesn’t find one, Python handles the exception by itself. This means it will print the exception and exit the program since it has no clue what to do with it.
If this feels abstract, don’t worry. The examples below will make it concrete, and you might want to come back to this section after finishing the article.
Catching exceptions with try except
To handle an exception, we need to catch it. As we just learned, we catch an exception with the try and except keywords. When an exception occurs while we are inside the try block, the code in the matching except block is executed.
A simple try except example
Let’s try a simple example first. As you hopefully know, we can’t divide by the number zero. If we do so anyway, Python raises an exception called ZeroDivisionError, which is a subclass of ArithmeticError:
try:
print(2/0)
except ZeroDivisionError:
print("You can't divide by zero!")Code language: Python (python)
You can't divide by zero!
If you call a Python function inside the try block, and an exception occurs in that function, the flow of code execution stops at the point of the exception and the code in the except block is executed. Try doing this again without try and except, and you’ll see that Python prints the exception for us:
print(2/0)Code language: Python (python)
Output:
Traceback (most recent call last):
File "divide_by_zero.py", line 1, in <module>
print(2/0)
~^~
ZeroDivisionError: division by zero
Also, note that Python prints the error to stderr if you don’t handle the exception yourself.
Python try except with a specific exception
Always name the exception you expect after except. That way you handle the one problem you planned for, and anything else still shows up as a normal error. Here we read an optional setting from a dictionary and fall back to a default when the key is missing:
config = {"host": "localhost"}
try:
port = config["port"]
except KeyError:
port = 8080
print(f"Connecting to port {port}")Code language: Python (python)
Connecting to port 8080
Only a KeyError is caught. If you made a typo and wrote confg["port"], you’d get a NameError with a clear traceback, which is exactly what you want while developing. (For this particular case, config.get("port", 8080) from the dictionaries article is shorter, but the pattern is the same for any operation that can fail.)
Catching IOError (OSError and FileNotFoundError)
Let’s try another, more common example. After all, who divides a number by zero, right?
Exceptions are likely to occur when interacting with the outside world, e.g., when working with files or networks. For example, if you try to open a file with Python, but that file doesn’t exist, you get a FileNotFoundError. If you don’t have access to a file due to permissions, you get a PermissionError. Both are subclasses of OSError.
You’ll still see IOError in older tutorials. Since Python 3.3 it’s just another name for OSError, so except IOError still works, but OSError (or the more specific subclasses) is what you should write today.
Assignment
Please do the following:
- Run the code below on your computer, and notice the file name (it doesn’t exist). See what happens.
- Create a file called myfile.txt in the same folder, change the file name in the code to myfile.txt, and rerun the code. What happens now?
Here’s the code to copy/paste:
try:
# Open file in read-only mode
with open("not_here.txt", 'r') as f:
f.write("Hello World!")
except OSError as e:
print("An error occurred:", e)Code language: Python (python)
Answers
The file is not found in the first case. You should get this output:
An error occurred: [Errno 2] No such file or directory: 'not_here.txt'
You’ll still get an error after creating the file in the second case. This time, we’re trying to write to a file that is opened in read-only mode. For more information on these modes, read the article on opening, reading, and writing files with Python. The error should look like this:
An error occurred: not writable
Although this is an error, it’s not written to the stderr output of the operating system. That’s because we handled the exception ourselves. If you remove the try and except from the code and then try to write to the file in read-only mode, the exception reaches the top of the program, Python stops the program, and shows this message:
Traceback (most recent call last): File “tryexcept.py”, line 2, inCatching multiple exceptions in Python
Often more than one thing can go wrong in the same piece of code. You have two options, depending on whether you want to handle the problems the same way or differently.
Catch multiple exceptions in one except block
If you want the same response to several exceptions, put them in parentheses as a tuple:
def parse_age(value):
try:
return int(value)
except (ValueError, TypeError):
return None
print(parse_age("39"))
print(parse_age("thirty-nine"))
print(parse_age(None))Code language: Python (python)
39 None None
int("thirty-nine") raises a ValueError, while int(None) raises a TypeError. One except block handles both.
Since Python 3.14, you may leave out the parentheses: except ValueError, TypeError: works too. The parentheses are still required when you add as e, and older Python versions need them in all cases, so I’d keep writing them.
Use multiple except blocks
If each problem needs its own response, write one except block per exception:
def read_number(path):
try:
with open(path) as f:
return int(f.read())
except FileNotFoundError:
print(f"{path} doesn't exist")
except ValueError:
print(f"{path} doesn't contain a number")
read_number("missing.txt")Code language: Python (python)
missing.txt doesn't exist
Python runs at most one except block: the first one that matches. Remember to put specific exceptions above more general ones.
The exception object: except Exception as e
Add as and a name to an except clause to get hold of the exception object itself. By convention, that name is e. The object tells you what went wrong:
try:
with open("settings.toml") as f:
settings = f.read()
except FileNotFoundError as e:
print("Message: ", e)
print("Type: ", type(e).__name__)
print("Filename: ", e.filename)
print("Args: ", e.args)Code language: Python (python)
Message: [Errno 2] No such file or directory: 'settings.toml' Type: FileNotFoundError Filename: settings.toml Args: (2, 'No such file or directory')
Printing e (or calling str(e)) gives you the error message. type(e).__name__ gives you the exception’s class name, which is handy when you catch several types in one block. Every exception has args, the values it was created with, and some have extra attributes, like filename on OSError and its subclasses.
One thing that surprises people: e only exists inside the except block. Python deletes it when the block ends:
try:
1 / 0
except ZeroDivisionError as e:
pass
print(e)Code language: Python (python)
Traceback (most recent call last):
File “example.py”, line 6, in If you need the exception later, assign it to another variable inside the except block.
How to print a Python exception
You can print exceptions directly as long as you catch them with as e. You’ve seen this above already, but to be clear, here’s the basic pattern:
try:
...
except Exception as e:
print("There was an error:", e)Code language: Python (python)
If you’d like to print the full traceback, just like Python does when you don’t catch the exception yourself, you can import the traceback module. The program keeps running afterward:
import traceback
try:
1 / 0
except ZeroDivisionError:
traceback.print_exc()
print("The program keeps running")Code language: Python (python)
Traceback (most recent call last):
File “example.py”, line 4, in In larger programs you’ll usually log errors instead of printing them. Inside an except block, logging.exception("Something failed") writes your message plus the full traceback to the log.
Python try except else and finally
Remember the illustration at the start of this article? Besides try and except, it has two more blocks: else and finally. Here’s the whole thing in one example:
def show_number(text):
try:
number = int(text)
except ValueError:
print(f"{text!r} is not a number")
else:
print(f"{number} squared is {number ** 2}")
finally:
print("Done with", repr(text))
show_number("12")
show_number("twelve")Code language: Python (python)
12 squared is 144 Done with '12' 'twelve' is not a number Done with 'twelve'
The else block ran only for "12", where no exception happened. The finally block ran both times. Let’s look at each in more detail.
The finally block in try-except
The finally block is executed regardless of whether an exception occurs or not. It even runs when the try block hits a return, or when an exception isn’t caught at all. That makes finally blocks useful when you want to close a file or a network connection no matter what happens, so you don’t leak resources.
Here’s an example in which we open a file without using the with statement, forcing us to close it ourselves:
def read_file(path):
f = open(path)
try:
return f.read()
finally:
print("Closing the file now")
f.close()
with open("myfile.txt", "w") as f:
f.write("Hello World!")
print(read_file("myfile.txt"))Code language: Python (python)
Closing the file now Hello World!
Notice that open() is outside the try block. If the file doesn’t exist, open() fails, there’s nothing to close, and the FileNotFoundError goes to the caller. If you put open() inside the try block, a missing file would make f.close() in the finally block fail with a NameError, because f was never assigned.
In practice, the with statement does this cleanup for you, and it’s what you should use for files. finally is for the cases where no context manager exists.
One warning: don’t put return, break or continue inside a finally block. They silently throw away any exception that was on its way out. Since Python 3.14, doing so gives a SyntaxWarning.
The else block in try-except
In addition to the except and finally blocks, you can add an else block. The else block executes only when no exception occurs in the try block. So it differs from the finally block, since finally executes even if an exception occurs.
When should you use the else block? And why shouldn’t you just add extra code to the try block? Good questions!
According to the Python manual, using the else clause is better than adding additional code to the try clause. The reasoning is that it avoids accidentally catching an exception that wasn’t raised by the code you meant to protect. In the show_number example above, only int(text) is inside the try block. If the print line in the else block somehow raised a ValueError, it would not be mistaken for bad input.
I admit I don’t use else blocks very often myself, and they can be confusing for people coming from other languages, which have nothing like it. A common alternative is to return from the try block or put the follow-up code after the whole try statement, as long as the try block itself stays small.
Raising (or throwing) exceptions
So far we’ve caught exceptions that Python raised. You can also raise an exception yourself with the raise keyword. Do this when your function gets something it can’t work with. Raising is better than returning a special value like None or -1, because the caller can’t accidentally ignore it.
def set_age(age):
if age < 0:
raise ValueError(f"age can't be negative, got {age}")
print(f"Age set to {age}")
set_age(39)
set_age(-1)Code language: Python (python)
Age set to 39
Traceback (most recent call last):
File “example.py”, line 7, in Pick the built-in exception that fits: ValueError for a value of the right type that’s still wrong, TypeError for the wrong type, KeyError or LookupError for something that can’t be found. And always include a message that says what was wrong.
Re-raise an exception
Sometimes you want to do something when an exception happens, like logging it, but you can’t actually fix the problem. In that case, use raise on its own inside the except block. It re-raises the same exception with its original traceback:
def load(path):
try:
with open(path) as f:
return f.read()
except FileNotFoundError:
print(f"Logging: {path} is missing")
raise
load("config.toml")Code language: Python (python)
Logging: config.toml is missing
Traceback (most recent call last):
File “example.py”, line 9, in Raise from: chaining exceptions
Often you’ll want to turn a low-level error into one that makes sense to the caller. A missing dictionary key or a failed int() means little to someone who just wants to load a config. Use raise ... from e to raise a new exception while keeping the original one attached:
class ConfigError(Exception):
pass
def read_port(settings):
try:
return int(settings["port"])
except (KeyError, ValueError) as e:
raise ConfigError("port must be set to a number") from e
read_port({"port": "eighty"})Code language: Python (python)
Traceback (most recent call last):
File “example.py”, line 6, in read_port
return int(settings[“port”])
ValueError: invalid literal for int() with base 10: ‘eighty’
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File “example.py”, line 10, in The traceback shows both exceptions: first the cause, then the new one. The caller only has to catch ConfigError, and whoever debugs it still sees the real reason. The original exception is also available as e.__cause__.
If the original exception is just noise, write raise ConfigError(...) from None. The traceback then shows only the new exception. Use this sparingly: hiding the cause makes debugging harder.
Add a note to an exception
Since Python 3.11, you can add extra context to an exception without replacing it, using add_note(). The note shows up below the error message:
rows = ["1", "2", "three"]
for line_number, row in enumerate(rows, start=1):
try:
value = int(row)
except ValueError as e:
e.add_note(f"while reading line {line_number} of data.csv")
raiseCode language: Python (python)
Traceback (most recent call last):
File “example.py”, line 5, in Create custom exceptions
All built-in, non-system-exiting exceptions are derived from the Exception class as we learned before. All user-defined exceptions should also be derived from this class. So if we want to create our own exceptions, we need to create a subclass of the Exception class.
For example, if you want to create an exception that indicates that a user was not found, you can create a UserNotFoundError exception. This would, in its most basic form, look like this:
class UserNotFoundError(Exception):
passCode language: Python (python)
This inherits all the properties and methods of Exception, but we give it a new name to distinguish it from the Exception class. This way, we’ll be able to specifically catch it with an except block. By convention, exception names end in Error.
The name of this exception clearly tells us the type of problem that was encountered, so as an added bonus, it works as code documentation as well. Just like well-named variables and functions, a well-named exception makes a big difference when reading back your code.
In the example below, we call a function, fetch_user, that fetches some user data from an imaginary database. If the user is not found, this database returns None. We decided that we don’t want to return None, which would force the caller to check for None every time. Instead, we raise our custom UserNotFoundError:
class UserNotFoundError(Exception):
pass
def fetch_user(user_id):
# Here you would fetch from some kind of db, e.g.:
# user = db.get_user(user_id)
# To make this example runnable, let's set it to None
user = None
if user is None:
raise UserNotFoundError(f"User {user_id} not in database")
return user
users = [123, 456, 789]
for user_id in users:
try:
fetch_user(user_id)
except UserNotFoundError as e:
print("There was an error:", e)Code language: Python (python)
There was an error: User 123 not in database There was an error: User 456 not in database There was an error: User 789 not in database
Assignment
Here’s a little assignment. We could have used a regular Exception object instead. That way, we don’t need to define a custom one. Why is this a bad idea?
Answer
You can in fact raise a regular exception, e.g. with raise Exception('User not found'). But if you do, you need to catch all exceptions of type Exception. And as we know, there are a lot of those. Chances are you inadvertently catch some other exception that you’re not able to handle. For example, the database client might throw a DatabaseAuthenticationError which is also a subclass of Exception.
Custom exceptions with extra data
Custom exceptions are ordinary classes, so you can give them attributes that the handler can use. In a bigger project, it also helps to define one base exception for your code and derive the others from it. Callers can then catch one specific error, or all of yours at once:
class ShopError(Exception):
"""Base class for all errors in our shop code."""
class OutOfStockError(ShopError):
def __init__(self, product, requested, available):
super().__init__(f"{product}: asked for {requested}, only {available} left")
self.product = product
self.available = available
class PaymentDeclinedError(ShopError):
pass
try:
raise OutOfStockError("coffee mug", 3, 1)
except OutOfStockError as e:
print(e)
print(f"We can still sell you {e.available}")
except ShopError as e:
print("Something else went wrong in the shop:", e)Code language: Python (python)
coffee mug: asked for 3, only 1 left We can still sell you 1
Calling super().__init__() with the message makes print(e) show something useful.
Exception groups and except* (Python 3.11+)
Normally, one exception happens at a time. But sometimes several independent things fail together: you validate a form and three fields are wrong, or you run five network requests at once and two of them fail. Python 3.11 added ExceptionGroup for this: one exception that carries a list of other exceptions.
To handle an exception group, use except* (pronounced “except star”). Each except* clause picks out the exceptions of its type from the group. Unlike a normal try statement, several except* clauses can run:
def validate(user):
errors = []
if not user.get("name"):
errors.append(ValueError("name is required"))
if not isinstance(user.get("age"), int):
errors.append(TypeError("age must be a whole number"))
if "email" not in user:
errors.append(KeyError("email"))
if errors:
raise ExceptionGroup("invalid user", errors)
try:
validate({"name": "", "age": "39"})
except* ValueError as eg:
for e in eg.exceptions:
print("Value problem:", e)
except* TypeError as eg:
for e in eg.exceptions:
print("Type problem:", e)
except* KeyError as eg:
for e in eg.exceptions:
print("Missing field:", e)Code language: Python (python)
Value problem: name is required Type problem: age must be a whole number Missing field: 'email'
Inside an except* block, the name after as is itself an exception group, holding only the matching exceptions. That’s why we loop over eg.exceptions.
Any exceptions in the group that no except* clause matches are raised again, still as a group:
try:
raise ExceptionGroup("two problems", [ValueError("bad value"), OSError("disk full")])
except* ValueError as eg:
print("Handled:", eg.exceptions)Code language: Python (python)
Handled: (ValueError(‘bad value’),)
+ Exception Group Traceback (most recent call last):
| File “example.py”, line 2, in You’ll meet exception groups most often in asyncio. When tasks in an asyncio.TaskGroup fail, their exceptions arrive together as a group:
import asyncio
async def fetch(name, fail):
await asyncio.sleep(0.1)
if fail:
raise ConnectionError(f"{name} is down")
return f"{name} ok"
async def main():
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(fetch("api", fail=True))
tg.create_task(fetch("db", fail=True))
tg.create_task(fetch("cache", fail=False))
except* ConnectionError as eg:
for e in eg.exceptions:
print("Failed:", e)
asyncio.run(main())Code language: Python (python)
Failed: api is down Failed: db is down
If you’re just starting out, you can skip exception groups for now. Regular except is what you’ll write 99% of the time. You can’t mix except and except* in the same try statement.
Common Python exceptions
Some exceptions are so common that you’ll inevitably encounter them. Here are the ones you’ll see most:
| Exception name | When you’ll encounter it | Example that raises it |
|---|---|---|
| SyntaxError | Raised when there is an error in Python syntax. Python finds it before running your code, so you can’t catch it in the same file. | print('test) |
| NameError | You use a variable or function name that doesn’t exist, often a typo. | pritn("hi") |
| TypeError | An operation gets a value of the wrong type. | "age: " + 39 |
| ValueError | The type is right, but the value isn’t. | int("ten") |
| KeyError | Raised when a key is not found in a dictionary. | d = {'a': 1} |
| IndexError | Raised when an index is out of range. | lst = [1, 2, 3] |
| AttributeError | An object doesn’t have the attribute or method you asked for. | None.upper() |
| ZeroDivisionError | You divide by zero. | 2 / 0 |
| FileNotFoundError | You open a file that doesn’t exist. | open("nope.txt") |
| KeyboardInterrupt | Raised when the user hits the interrupt key (Ctrl+C) | Pressing control+c |
If you like, you can try to trigger these exceptions on purpose. I promise you that you will encounter these countless times in your Python programming career. Understanding what they mean and when they occur will greatly help you debug your code.
Python try except best practices
Now that we know the mechanics of handling exceptions, I’d like to share a couple of best practices with you. Most of them come down to one idea: handle the errors you expect and know how to deal with, and let everything else crash loudly.
Catch narrowly: specific exceptions, small try blocks
Catch the most specific exception you can, and only catch it if you have a real plan for it, such as a default value, a retry, or a clear message to the user. Also keep the try block small: wrap only the line that can fail, not the whole function. A big try block can catch an exception from a line you didn’t expect to fail, and then your handler does the wrong thing:
prices = {"apple": 0.5, "pear": 0.75}
def price_of(item):
try:
price = prices[item]
except KeyError:
return None
return round(price * 1.21, 2)
print(price_of("apple"))
print(price_of("kiwi"))Code language: Python (python)
0.6 None
Only the dictionary lookup is inside the try block. A bug in the calculation below it will still show up as an error instead of quietly returning None.
Don’t silence errors
The worst exception handler is this one:
try:
do_something()
except Exception:
passCode language: Python (python)
Your program keeps running as if nothing happened, possibly with missing or wrong data, and you have no idea anything failed. When you do handle an error, leave a trace. Log it, show a message, or re-raise it:
import logging
logging.basicConfig(format="%(levelname)s: %(message)s")
def load_avatar(path):
try:
with open(path, "rb") as f:
return f.read()
except FileNotFoundError:
logging.warning("No avatar at %s, using the default", path)
return b""
load_avatar("avatars/erik.png")Code language: Python (python)
WARNING: No avatar at avatars/erik.png, using the default
Sometimes ignoring an exception really is correct, like deleting a file that may already be gone. Then say so explicitly with contextlib.suppress, and name the exact exception:
import os
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("old_cache.tmp")
print("Cache cleared (or there was nothing to clear)")Code language: Python (python)
Cache cleared (or there was nothing to clear)
Don’t use a bare except
I’ve written about this in the blog post ‘How not to handle exceptions in Python‘. Don’t use a blank (or “bare”) except when you want to catch a broad range of exceptions. By this, I mean something like:
try:
...
except:
print("An error occurred")Code language: Python (python)
You might encounter this in code samples on the web. If you do, make a habit of improving the exception handling. Why should you, and how can you improve code like the example above?
All exceptions, including system exceptions, inherit from a class called BaseException. If an except clause mentions a particular class, that clause also handles any exception classes derived from that class. An empty except is equivalent to except BaseException, hence it will catch all possible exceptions.
So, although the syntax is allowed, I don’t recommend it. You’ll also catch KeyboardInterrupt and SystemExit, which means Ctrl+C and sys.exit() no longer stop your program. Instead, catch the explicit exceptions you can handle. Or, if you really need to, catch the Exception base class to handle almost all the regular exceptions, but not the system ones.
If you’re feeling adventurous, you can try to catch all exceptions and see what happens:
from time import sleep
while True:
try:
print("Try and stop me")
sleep(1)
except:
print("Don't stop me now, I'm having such a good time!")
Pressing Ctrl+C won’t stop this program; you’ll probably need to close your terminal. Now change the except block to catch Exception. You will still catch almost all exceptions, but the program will exit on system exceptions like KeyboardInterrupt and SystemExit:
from time import sleep
while True:
try:
print("Try and stop me")
sleep(1)
except Exception:
print("Something went wrong")
It’s better to ask for forgiveness
In Python, you’ll often see a pattern where people simply try if something works, and if it doesn’t, catch the exception. In other words, it’s better to ask for forgiveness than permission (EAFP). This is in contrast to other languages, where you preferably ask for permission. For example, in Java, exceptions can slow down your program, and you “ask for permission” by doing checks on an object instead of simply trying.
To make this more concrete: in Python, we often just try to access the key in a dictionary. If the key doesn’t exist, we’ll get an exception and handle it. Suppose we just converted some externally provided JSON to a dictionary and now start to use it:
import json
user_json = '{"name": "John", "age": 39}'
user = json.loads(user_json)
try:
print(user['name'])
print(user['age'])
print(user['address'])
except KeyError as e:
print("There are missing fields in the user object:", e)Code language: Python (python)
This prints:
John 39 There are missing fields in the user object: 'address'
We could have added three checks (if 'name' in user, if 'age' in user, etc.) to make sure that all the fields are there. But that adds a lot of code just to check if keys exist. Instead, we ask for forgiveness in our except block once, which is cleaner and more readable. And if you worry about performance: exceptions don’t take up that many CPU cycles in Python. Lots of comparisons are often slower than catching a single exception (if it occurs at all!).
Keep learning
Here are some more resources to deepen your knowledge:
- My blog article ‘How not to handle exceptions in Python‘
- Introduction to Python functions
- Objects and classes, and Python inheritance
- The official documentation on exceptions.
- The official documentation on errors.