🕷️ Crawler Inspector

URL Lookup

Direct Parameter Lookup

Raw Queries and Responses

1. Shard Calculation

Query:
Response:
Calculated Shard: 197 (from laksa143)

2. Crawled Status Check

Query:
Response:

3. Robots.txt Check

Query:
Response:

4. Spam/Ban Check

Query:
Response:

5. Seen Status Check

ℹ️ Skipped - page is already crawled

đź“„
INDEXABLE
âś…
CRAWLED
3 days ago
🤖
ROBOTS ALLOWED

Page Info Filters

FilterStatusConditionDetails
HTTP statusPASSdownload_http_code = 200HTTP 200
Age cutoffPASSdownload_stamp > now() - 6 MONTH0.1 months ago
History dropPASSisNull(history_drop_reason)No drop reason
Spam/banPASSfh_dont_index != 1 AND ml_spam_score = 0ml_spam_score=0
CanonicalPASSmeta_canonical IS NULL OR = '' OR = src_unparsedNot set

Page Details

PropertyValue
URLhttps://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/
Last Crawled2026-04-04 18:55:23 (3 days ago)
First Indexed2023-08-31 09:02:05 (2 years ago)
HTTP Status Code200
Meta TitleThe ultimate guide to Python exception handling - Honeybadger Developer Blog
Meta DescriptionDiscover powerful Python exception-handling techniques with this guide. Learn about nested try blocks, re-raising exceptions, custom exceptions, and more.
Meta Canonicalnull
Boilerpipe Text
Exceptions can occur for various reasons, such as invalid input, logical errors, file handling issues, network problems, or other exception conditions. Examples of exceptions in Python include ZeroDivisionError , TypeError , FileNotFoundError , and ValueError , among others. Exception handling in Python is a crucial aspect of writing robust and reliable code in Python. This tutorial aims to provide a comprehensive understanding of Python exception handling techniques and provide examples of how those techniques can be used practically. By the end of the tutorial, you’ll know how to build reliable programs. Let’s get started! What is an exception? In Python, an exception is an event that occurs during the execution of a program and disrupts the normal flow of the program. It represents an error or an exception condition that the program encounters and cannot handle by itself. Even with a great testing suite , Python exceptions can happen and cause problems for users. When an exception occurs, it is "raised" or "thrown" by the Python interpreter. The exception then propagates up the call stack, searching for an exception handler that can catch and handle the exception. If no suitable exception handler is found, the program terminates, and an error message is displayed. Exceptions differ from syntax errors because syntax errors happen when Python translates your code to bytecode. Syntax errors prevent your program from running at all. Exceptions, on the other hand, are considered runtime errors, which can pop up during the normal execution of otherwise valid code. For example, dividing by zero doesn’t break Python’s syntax rules; it’s valid syntax. But it’s a mathematical impossibility, so it raises an exception. Specifically, it raises a ZeroDivisionError. Another key thing to remember about exceptions is that they’re not necessarily catastrophic. They’re special Python objects that can be caught and handled if you can foresee them coming. With proper exception handling, your program has the opportunity to respond to unexpected situations like missing files or invalid user input more gracefully. Instead of crashing, your code can log the error, show a user-friendly message, or even use some predetermined fallback logic. This makes exception handling essential for writing robust applications that can withstand a wide range of real-world scenarios. Here’s an example of a ZeroDivisionError exception being raised and handled using a try-except block: try : result = 10 / 0 # Raises ZeroDivisionError except ZeroDivisionError : print ( "Error: Division by zero!" ) In this example, the code within the try block raises a ZeroDivisionError exception when attempting to divide by zero. The exception is caught by the except block, and the specified error message is printed, allowing the program to continue execution instead of abruptly terminating. Nested try-except blocks Nested try-except blocks provide a way to handle specific exceptions at different levels of code execution. This technique allows you to catch and handle exceptions more precisely based on the context in which they occur. Consider the following example: try : # Outer try block try : # Inner try block file = open ( "nonexistent_file.txt" , "r" ) content = file . read () file . close () print ( "File content:" , content) except FileNotFoundError : print ( "Error: File not found!" ) except : print ( "Error: Outer exception occurred!" ) Output: Error: File not found! In this example, the inner Python try block attempts to open a file "nonexistent_file.txt" in read mode, which doesn’t exist and raises a FileNotFoundError . The exception is caught by the inner except block, which prints the error message "Error: File not found!". Since the exception is handled within the inner except block, the outer except block is not executed. However, if the inner except block was not executed, the exception would propagate to the outer except block, and the code within the outer except block would be executed. Using try-except-else-finally blocks A powerful feature of Python’s exception-handling paradigm is the option to include else and finally clauses alongside try-except . This structure provides a clear separation of concerns for different parts of your code, making it more readable and robust: def safe_divide ( a , b ): try : result = a / b except ZeroDivisionError : print ( "Please don't divide by zero" ) else : print ( "Thank you for dividing" ) finally : print ( "This happens all the time" ) safe_divide ( 10 , 0 ) safe_divide ( 20 , 2 ) The try block is the primary block where you attempt to execute code that might throw an exception. In our example, that’s a division operation. The except block catches any exceptions that occur within the try block. In this particular example, that’s a ZeroDivisionError . The else block executes only if no exceptions are raised in the try block. It’s a good place for code that only runs once everything has succeeded. The finally block executes regardless of whether an exception occurred; it is commonly used for cleanup actions like closing files, releasing locks, or cleaning up resources. This structured flow helps you handle errors more precisely, keeps the “happy path” logic separate, and ensures that any necessary teardown always happens. Handling multiple exceptions in a single except block Sometimes, you might want to catch multiple types of exceptions, but you want to treat them the same. You can catch multiple exceptions with identical handling logic. Python lets you group them in a single except clause using parentheses. Check out this example: def fetch_data ( source ): if source == "file" : raise FileNotFoundError ( "File source missing!" ) elif source == "api" : raise ConnectionError ( "Failed to connect to API!" ) else : return "Data fetched successfully!" sources = [ "file" , "api" , "database" ] for src in sources : try : data = fetch_data (src) print (data) except ( FileNotFoundError , ConnectionError ) as e : print ( "Recoverable error:" , e) except Exception as ex : print ( "Unknown error:" , ex) In this snippet, you’ll notice a few things. First, both the FileNotFoundError and the ConnectionError are handled in the same way. Second, you’ll notice that the except Exception acts as a fallback if some other unanticipated exception type arises. Grouping exceptions is particularly helpful when you know that your remediation steps for certain errors will be identical. This approach prevents repetitive code and keeps your error handling clear and concise. Catching and re-raising exceptions Catching and re-raising exceptions is a useful technique when you need to handle an exception at a specific level of code execution, perform certain actions, and then allow the exception to reproduce to higher levels for further handling. Let’s explore the example further and discuss its significance. In the provided code snippet, the validate_age function takes an age parameter and checks if it is negative. If the age is negative, a ValueError is raised using the raise keyword. The exception is then caught by the except block that specifies ValueError as the exception type. def validate_age ( age ): try : if age < 0 : raise ValueError ( "Age cannot be negative!" ) except ValueError as ve : print ( "Error:" , ve) raise # Re-raise the exception try : validate_age ( - 5 ) except ValueError : print ( "Caught the re-raised exception!" ) In this case, if the age provided to validate_age is -5, the condition if age < 0 is satisfied, and a ValueError is raised with the message "Age cannot be negative!". The except block then catches the ValueError and prints the error message using print("Error:", ve) . This step allows you to perform specific actions, such as logging the error or displaying a user-friendly error message. After printing the error message, the raise statement is used to re-raise the caught exception. This re-raised exception propagates to a higher level of code execution, allowing it to be caught by an outer exception handler if present. The output of this code snippet is: Error: Age cannot be negative! Caught the re-raised exception! This example demonstrates the importance of catching and re-raising exceptions. By catching an exception, performing necessary actions, and re-raising it, you have more control over how the exception is handled at different levels of your code. Handling exceptions in loops Handling exceptions in loops is essential to ensure the smooth execution of code and prevent premature termination. By incorporating exception handling within loops, you can gracefully handle specific exceptions and continue the loop iteration. Let’s explore the example further to understand its significance. numbers = [ 1 , 2 , 3 , 0 , 4 , 5 ] for num in numbers : try : result = 10 / num print ( f "Result: { result } " ) except ZeroDivisionError : print ( "Error: Division by zero!" ) except Exception as e : print ( "Unknown Error:" , e) In this code snippet, we have a loop that iterates over a list of numbers. For each number, a division operation is performed by dividing 10 by the current number. If the number is zero, a ZeroDivisionError is raised. The first except ZeroDivisionError block catches this exception and prints the error message "Error: Division by zero!". Additionally, there is a generic except Exception as e block to catch any other exceptions that may occur during the loop iteration. The output of this code snippet is: Result: 10.0 Result: 5.0 Result: 3.3333333333333335 Error: Division by zero! Result: 2.5 Result: 2.0 As we can see from the output, the loop successfully performs the division operation for non-zero numbers and prints the results. When encountering a zero, the ZeroDivisionError is caught, and the error message is printed. The loop continues to execute, handling each iteration gracefully, even in the presence of exceptions. Handling exceptions in asynchronous code ( asyncio ) Handling exceptions in asynchronous code can be crucial to ensure the stability and proper functioning of asynchronous tasks. The asyncio library in Python provides tools and mechanisms to handle exceptions in async tasks effectively. import asyncio async def divide ( a , b ): return a / b async def main (): try : await divide ( 10 , 0 ) except ZeroDivisionError : print ( "Error: Division by zero!" ) asyncio . run ( main ()) In this code snippet, we have an asynchronous function named divide that performs a division operation on two numbers. The division is executed using the division operator / , which may raise a ZeroDivisionError if the denominator is zero. Within this function, we use the try-except block to catch any ZeroDivisionError that may occur during the execution of the divide coroutine. The output of this code snippet is: Error: Division by zero! As expected, the division by zero raises a ZeroDivisionError exception, which is caught by the except ZeroDivisionError block. The error message "Error: Division by zero!" is then printed. Handling exceptions in asynchronous code with asyncio involves using the try-except block within the context of an async task. Get the Honeybadger Newsletter Join our community of kick-ass developers as we learn engineering, DevOps, and cloud architecture. We'll never spam you; we will send you cool stuff like exclusive content, memes, and occasional giveaways. Developing custom exceptions Python allows software developers to create custom Python exception classes to handle specific types of errors within their applications. Creating custom exception classes provides a novel way to organize and categorize errors, making code more maintainable and enabling more granular error handling. With these benefits in mind, let’s now explore the process of creating custom exception classes in Python. To create a custom exception class, you need to define a new class that inherits from one of the built-in exception classes or the base Exception class. Here is a simple example: import logging class CustomException ( Exception ): def __init__ ( self , message ): super (). __init__ (message) self . error_code = 1001 def log_error ( self ): logger = logging . getLogger ( "custom_logger" ) logger . error ( f "Custom Exception occurred: { self } " ) def divide ( a , b ): if b == 0 : raise CustomException ( "Division by zero is not allowed." ) return a / b try : result = divide ( 10 , 0 ) except CustomException as ce : print ( "Error code:" , ce.error_code) ce . log_error () Output: Error code: 1001 Custom Exception occurred: Division by zero is not allowed. In the code above, the CustomException class is defined, inheriting from the base Exception class. It has an additional attribute, error_code , which is set to 1001 in the constructor ( __init__ method). The super().__init__(message) call initializes the base Exception class with the given error message. The log_error method retrieves or creates a logger named "custom_logger" using logging.getLogger("custom_logger") . The logger.error method is used to log an error message indicating that a custom exception occurred. The {self} expression in the log message includes the string representation of the exception object. The divide function performs division between two numbers. If the divisor ( b ) is zero, the CustomException is triggered with the error message "Division by zero is not allowed." The division operation is attempted within a try-except block. If a CustomException is raised, it is caught using the except CustomException as ce block. The error code of the exception is printed ( print("Error code:", ce.error_code) ) to demonstrate access to the error_code attribute of the CustomException instance. Finally, the log_error method is invoked ( ce.log_error() ) to log the exception. Best practices to keep in mind for Python exception handling Python’s flexibility makes catching and raising exceptions straightforward, but using this carefully is essential. Poorly structured exception handling can make code messy and unpredictable. Following some established standards to keep your exception handling straightforward is in your best interest. Here are a few best practices to keep in mind: Don’t use too broad of a catch Avoid using overly broad except Exception: blocks unless it’s absolutely necessary. Broad catches can mask programming errors (like typos or bad logic) that should surface for debugging. Instead, handle specific exception types whenever possible. Log and re-raise exceptions to keep track of them If you’re catching an exception solely to log or perform housekeeping before shutting down, consider re-raising the same exception. This preserves the original traceback and makes debugging easier. If you catch an exception just to log it, you’ll probably have some unexpected application flow. Separate exception recovery from business logic Keep your “happy path” business logic decoupled from the fallback behavior. Exceptional cases should not be normal. This makes your code cleaner. Leverage the else clause on try blocks for code that should only run if no exception was raised. Document expected failures Good documentation or even simple comments across your codebase can go a long way toward clarifying which exceptions a function can raise. This helps your team (or even your future self) confidently write code without guesswork. Logging exceptions to an error monitoring service It’s inevitable that you’ll eventually come across errors and exceptions when developing software applications. Monitoring and managing these Python errors effectively is crucial for maintaining the stability and performance of an application. One effective approach is to log exceptions to an error-monitoring service. Such services provide valuable insights into the occurrence and impact of errors, enabling developers to identify and resolve issues promptly. By integrating an error-monitoring service into the development workflow, teams can enhance their debugging and troubleshooting capabilities, leading to improved user experience and overall application quality. Examples of some error monitoring services are Honeybadger, Sentry, Datadog, etc. Integrating Honeybadger for error monitoring Honeybadger is a powerful error-monitoring tool for Python applications. Integrating an error monitoring service like Honeybadger into your development workflow provides numerous benefits for effectively managing exceptions. From real-time notifications and error grouping to rich diagnostics and trend analysis, Honeybadger equips you with the tools you need to quickly identify, investigate, and resolve errors and ultimately enhance the overall quality and reliability of your applications. To show off some of this cool functionality, let’s now explore some features and examples of integrating Honeybadger into your Python code. To use Honeybadger you need to first get an API key, which you get by signing up for a free Honeybadger account . On the Honeybadger projects page , click on the “ Create your first project ” button, give your project a name, and finally click the “ Settings ” tab, and you will be able to access your API key there. You will also need to install Honeybadger locally. You can do that with the following command: pip install honeybadger Get Honeybadger's best Python articles in your inbox We publish 1-2 times per month. Subscribe to get our Python articles as soon as we publish them. We'll never spam you; we will send you cool stuff like exclusive content, memes, and occasional giveaways. Customizing Error Notifications Honeybadger allows you to customize error notifications based on your application’s needs. You can specify the severity level, tags, and additional context to be included in the error reports. Here’s an example: from honeybadger import honeybadger # Configure Honeybadger with your API key honeybadger . configure (api_key = 'hbp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' ) # paste your API key def divide ( a , b ): result = a / b return result # Example usage of the divide function try : result = divide ( 10 , 0 ) except Exception as e : honeybadger . notify (e, context = { 'user_id' : 123 }, error_message = 'Division by zero is not allowed.' ) #'Division by zero is not allowed.' will show if `e` was not passed In this example, honeybadger.notify() the context parameter provides additional contextual information, such as the user ID associated with the error. To manually send an error message to Honeybadger notification, you add the “error_message argument. The message in error_message will show up only when there is no error passed in the honeybadger.notify() method. When you run the above code, you will receive in your email a message notifying you of the error, like the message that can be seen below. Handling Python exceptions in the wild Python exception handling is a critical aspect of writing robust and reliable Python code. By mastering various techniques, from nested try-except blocks to creating custom exceptions, you can enhance the reliability of your code. Additionally, error tracking tools like Honeybadger can monitor for exceptions, allowing you to proactively identify and address issues in your Python applications. Editor's note: This post was originally published in August 2023 and has been updated for accuracy.
Markdown
[Honeybadger ![Honeybadger homepage](https://www.honeybadger.io/_astro/logo.C8JJJNhL.svg) ![Honeybadger homepage](https://www.honeybadger.io/_astro/logo-dark.B42c-pvU.svg)](https://www.honeybadger.io/) Open main menu Product Features [Error Tracking](https://www.honeybadger.io/tour/error-tracking/) [Logging & Observability](https://www.honeybadger.io/tour/logging-observability/) [Dashboards & APM](https://www.honeybadger.io/tour/dashboards/) [Uptime Monitoring](https://www.honeybadger.io/tour/uptime-monitoring/) [Cron & Heartbeat Monitoring](https://www.honeybadger.io/tour/cron-job-heartbeat-monitoring/) [Status Pages](https://www.honeybadger.io/tour/status-pages/) Languages [Ruby](https://www.honeybadger.io/for/ruby/) [JavaScript](https://www.honeybadger.io/for/javascript/) [Python](https://www.honeybadger.io/for/python/) [Elixir](https://www.honeybadger.io/for/elixir/) [PHP](https://www.honeybadger.io/for/php/) [All languages](https://www.honeybadger.io/platforms/) Integrations [GitHub](https://www.honeybadger.io/integrations/github/) [Slack](https://www.honeybadger.io/integrations/slack/) [Datadog](https://www.honeybadger.io/vs/datadog/) [PagerDuty](https://www.honeybadger.io/integrations/pagerduty/) [All integrations](https://www.honeybadger.io/integrations/) [Pricing](https://www.honeybadger.io/plans/) [Changelog](https://www.honeybadger.io/changelog/) [Blog](https://www.honeybadger.io/blog/) [Log in](https://app.honeybadger.io/users/sign_in) [Start free trial](https://app.honeybadger.io/users/sign_up?plan=team) [Honeybadger ![](https://www.honeybadger.io/_astro/bolt.D0BsYe2K.svg) ![](https://www.honeybadger.io/_astro/bolt-dark.DI2bt8qx.svg)](https://www.honeybadger.io/) Close menu Product Features [Error Tracking](https://www.honeybadger.io/tour/error-tracking/) [Logging & Observability](https://www.honeybadger.io/tour/logging-observability/) [Dashboards & APM](https://www.honeybadger.io/tour/dashboards/) [Uptime Monitoring](https://www.honeybadger.io/tour/uptime-monitoring/) [Cron & Heartbeat Monitoring](https://www.honeybadger.io/tour/cron-job-heartbeat-monitoring/) [Status Pages](https://www.honeybadger.io/tour/status-pages/) Languages [Ruby](https://www.honeybadger.io/for/ruby/) [JavaScript](https://www.honeybadger.io/for/javascript/) [Python](https://www.honeybadger.io/for/python/) [Elixir](https://www.honeybadger.io/for/elixir/) [PHP](https://www.honeybadger.io/for/php/) [All languages](https://www.honeybadger.io/platforms/) Integrations [GitHub](https://www.honeybadger.io/integrations/github/) [Slack](https://www.honeybadger.io/integrations/slack/) [Datadog](https://www.honeybadger.io/vs/datadog/) [PagerDuty](https://www.honeybadger.io/integrations/pagerduty/) [All integrations](https://www.honeybadger.io/integrations/) [Pricing](https://www.honeybadger.io/plans/) [Changelog](https://www.honeybadger.io/changelog/) [Blog](https://www.honeybadger.io/blog/) [Log in](https://app.honeybadger.io/users/sign_in) [Start free trial](https://app.honeybadger.io/users/sign_up?plan=team) [Book a demo](https://www.honeybadger.io/demo/) [Honeybadger Developer Blog](https://www.honeybadger.io/blog/) [Company updates](https://www.honeybadger.io/blog/company/) [Ruby](https://www.honeybadger.io/blog/ruby/) [JavaScript](https://www.honeybadger.io/blog/javascript/) [Python](https://www.honeybadger.io/blog/python/) [DevOps](https://www.honeybadger.io/blog/devops/) [Go](https://www.honeybadger.io/blog/go/) [Elixir](https://www.honeybadger.io/blog/elixir/) [Write for us](https://www.honeybadger.io/blog/write-for-us/) On this page What is an exception? - [Overview](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/) - [What is an exception?](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#what-is-an-exception) - [Nested try-except blocks](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#nested-try-except-blocks) - [Using try-except-else-finally blocks](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#using-try-except-else-finally-blocks) - [Handling multiple exceptions in a single except block](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-multiple-exceptions-in-a-single-except-block) - [Catching and re-raising exceptions](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#catching-and-re-raising-exceptions) - [Handling exceptions in loops](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-exceptions-in-loops) - [Handling exceptions in asynchronous code (asyncio)](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-exceptions-in-asynchronous-code-asyncio) - [Developing custom exceptions](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#developing-custom-exceptions) - [Best practices to keep in mind for Python exception handling](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#best-practices-to-keep-in-mind-for-python-exception-handling) - [Logging exceptions to an error monitoring service](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#logging-exceptions-to-an-error-monitoring-service) - [Handling Python exceptions in the wild](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-python-exceptions-in-the-wild) - [Honeybadger Developer Blog](https://www.honeybadger.io/blog/) - [Company updates](https://www.honeybadger.io/blog/company/) - [Ruby](https://www.honeybadger.io/blog/ruby/) - [JavaScript](https://www.honeybadger.io/blog/javascript/) - [Python](https://www.honeybadger.io/blog/python/) - [DevOps](https://www.honeybadger.io/blog/devops/) - [Go](https://www.honeybadger.io/blog/go/) - [Elixir](https://www.honeybadger.io/blog/elixir/) - [Write for us](https://www.honeybadger.io/blog/write-for-us/) - [RSS Feed](https://www.honeybadger.io/blog/feed.xml) Mar 28, 2025 · [Muhammed Ali](https://www.honeybadger.io/blog/authors/muhammedali/) · [.md](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python.md "View as Markdown") # The ultimate guide to Python exception handling Exceptions can occur for various reasons, such as invalid input, logical errors, file handling issues, network problems, or other exception conditions. Examples of exceptions in Python include `ZeroDivisionError`, `TypeError`, `FileNotFoundError`, and `ValueError`, among others. Exception handling in Python is a crucial aspect of writing robust and reliable code in Python. This tutorial aims to provide a comprehensive understanding of Python exception handling techniques and provide examples of how those techniques can be used practically. By the end of the tutorial, you’ll know how to build reliable programs. Let’s get started\! ## What is an exception? In Python, an exception is an event that occurs during the execution of a program and disrupts the normal flow of the program. It represents an error or an exception condition that the program encounters and cannot handle by itself. Even with a [great testing suite](https://www.honeybadger.io/blog/beginners-guide-to-software-testing-in-python/), Python exceptions can happen and cause problems for users. When an exception occurs, it is "raised" or "thrown" by the Python interpreter. The exception then propagates up the call stack, searching for an exception handler that can catch and handle the exception. If no suitable exception handler is found, the program terminates, and an error message is displayed. Exceptions differ from syntax errors because syntax errors happen when Python translates your code to bytecode. Syntax errors prevent your program from running at all. Exceptions, on the other hand, are considered runtime errors, which can pop up during the normal execution of otherwise valid code. For example, dividing by zero doesn’t break Python’s syntax rules; it’s valid syntax. But it’s a mathematical impossibility, so it raises an exception. Specifically, it raises a `ZeroDivisionError.` Another key thing to remember about exceptions is that they’re not necessarily catastrophic. They’re special Python objects that can be caught and handled if you can foresee them coming. With proper exception handling, your program has the opportunity to respond to unexpected situations like missing files or invalid user input more gracefully. Instead of crashing, your code can log the error, show a user-friendly message, or even use some predetermined fallback logic. This makes exception handling essential for writing robust applications that can withstand a wide range of real-world scenarios. Here’s an example of a `ZeroDivisionError` exception being raised and handled using a `try-except` block: ``` try: result = 10 / 0 # Raises ZeroDivisionError except ZeroDivisionError: print("Error: Division by zero!") ``` In this example, the code within the `try` block raises a `ZeroDivisionError` exception when attempting to divide by zero. The exception is caught by the `except` block, and the specified error message is printed, allowing the program to continue execution instead of abruptly terminating. ## Nested `try-except` blocks Nested try-except blocks provide a way to handle specific exceptions at different levels of code execution. This technique allows you to catch and handle exceptions more precisely based on the context in which they occur. Consider the following example: ``` try: # Outer try block try: # Inner try block file = open("nonexistent_file.txt", "r") content = file.read() file.close() print("File content:", content) except FileNotFoundError: print("Error: File not found!") except: print("Error: Outer exception occurred!") ``` Output: ``` Error: File not found! ``` In this example, the inner Python try block attempts to open a file "nonexistent\_file.txt" in read mode, which doesn’t exist and raises a `FileNotFoundError`. The exception is caught by the inner except block, which prints the error message "Error: File not found!". Since the exception is handled within the inner except block, the outer except block is not executed. However, if the inner except block was not executed, the exception would propagate to the outer except block, and the code within the outer except block would be executed. ## Using `try-except-else-finally` blocks A powerful feature of Python’s exception-handling paradigm is the option to include `else` and `finally` clauses alongside `try-except`. This structure provides a clear separation of concerns for different parts of your code, making it more readable and robust: ``` def safe_divide(a, b): try: result = a / b except ZeroDivisionError: print("Please don't divide by zero") else: print("Thank you for dividing") finally: print("This happens all the time") safe_divide(10, 0) safe_divide(20, 2) ``` The `try` block is the primary block where you attempt to execute code that might throw an exception. In our example, that’s a division operation. The `except` block catches any exceptions that occur within the try block. In this particular example, that’s a `ZeroDivisionError`. The `else` block executes only if no exceptions are raised in the try block. It’s a good place for code that only runs once everything has succeeded. The `finally` block executes regardless of whether an exception occurred; it is commonly used for cleanup actions like closing files, releasing locks, or cleaning up resources. This structured flow helps you handle errors more precisely, keeps the “happy path” logic separate, and ensures that any necessary teardown always happens. ### Try Honeybadger for FREE Error tracking and performance monitoring in one simple interface. [Start free trial](https://app.honeybadger.io/users/sign_up?plan=team) No credit card required ## Handling multiple exceptions in a single except block Sometimes, you might want to catch multiple types of exceptions, but you want to treat them the same. You can catch multiple exceptions with identical handling logic. Python lets you group them in a single except clause using parentheses. Check out this example: ``` def fetch_data(source): if source == "file": raise FileNotFoundError("File source missing!") elif source == "api": raise ConnectionError("Failed to connect to API!") else: return "Data fetched successfully!" sources = ["file", "api", "database"] for src in sources: try: data = fetch_data(src) print(data) except (FileNotFoundError, ConnectionError) as e: print("Recoverable error:", e) except Exception as ex: print("Unknown error:", ex) ``` In this snippet, you’ll notice a few things. First, both the `FileNotFoundError` and the `ConnectionError` are handled in the same way. Second, you’ll notice that the `except Exception` acts as a fallback if some other unanticipated exception type arises. Grouping exceptions is particularly helpful when you know that your remediation steps for certain errors will be identical. This approach prevents repetitive code and keeps your error handling clear and concise. ## Catching and re-raising exceptions Catching and re-raising exceptions is a useful technique when you need to handle an exception at a specific level of code execution, perform certain actions, and then allow the exception to reproduce to higher levels for further handling. Let’s explore the example further and discuss its significance. In the provided code snippet, the `validate_age` function takes an `age` parameter and checks if it is negative. If the age is negative, a `ValueError` is raised using the `raise` keyword. The exception is then caught by the except block that specifies `ValueError` as the exception type. ``` def validate_age(age): try: if age < 0: raise ValueError("Age cannot be negative!") except ValueError as ve: print("Error:", ve) raise # Re-raise the exception try: validate_age(-5) except ValueError: print("Caught the re-raised exception!") ``` In this case, if the age provided to `validate_age` is -5, the condition `if age < 0` is satisfied, and a `ValueError` is raised with the message "Age cannot be negative!". The except block then catches the `ValueError` and prints the error message using `print("Error:", ve)`. This step allows you to perform specific actions, such as [logging the error](https://www.honeybadger.io/blog/python-logging/) or displaying a user-friendly error message. After printing the error message, the `raise` statement is used to re-raise the caught exception. This re-raised exception propagates to a higher level of code execution, allowing it to be caught by an outer exception handler if present. The output of this code snippet is: ``` Error: Age cannot be negative! Caught the re-raised exception! ``` This example demonstrates the importance of catching and re-raising exceptions. By catching an exception, performing necessary actions, and re-raising it, you have more control over how the exception is handled at different levels of your code. ## Handling exceptions in loops Handling exceptions in loops is essential to ensure the smooth execution of code and prevent premature termination. By incorporating exception handling within loops, you can gracefully handle specific exceptions and continue the loop iteration. Let’s explore the example further to understand its significance. ``` numbers = [1, 2, 3, 0, 4, 5] for num in numbers: try: result = 10 / num print(f"Result: {result}") except ZeroDivisionError: print("Error: Division by zero!") except Exception as e: print("Unknown Error:", e) ``` In this code snippet, we have a loop that iterates over a list of numbers. For each number, a division operation is performed by dividing `10` by the current number. If the number is zero, a `ZeroDivisionError` is raised. The first `except ZeroDivisionError` block catches this exception and prints the error message "Error: Division by zero!". Additionally, there is a generic `except Exception as e` block to catch any other exceptions that may occur during the loop iteration. The output of this code snippet is: ``` Result: 10.0 Result: 5.0 Result: 3.3333333333333335 Error: Division by zero! Result: 2.5 Result: 2.0 ``` As we can see from the output, the loop successfully performs the division operation for non-zero numbers and prints the results. When encountering a zero, the `ZeroDivisionError` is caught, and the error message is printed. The loop continues to execute, handling each iteration gracefully, even in the presence of exceptions. ## Handling exceptions in asynchronous code (`asyncio`) Handling exceptions in asynchronous code can be crucial to ensure the stability and proper functioning of asynchronous tasks. The **`asyncio`** library in Python provides tools and mechanisms to handle exceptions in async tasks effectively. ``` import asyncio async def divide(a, b): return a / b async def main(): try: await divide(10, 0) except ZeroDivisionError: print("Error: Division by zero!") asyncio.run(main()) ``` In this code snippet, we have an asynchronous function named `divide` that performs a division operation on two numbers. The division is executed using the division operator `/`, which may raise a `ZeroDivisionError` if the denominator is zero. Within this function, we use the `try-except` block to catch any `ZeroDivisionError` that may occur during the execution of the `divide` coroutine. The output of this code snippet is: ``` Error: Division by zero! ``` As expected, the division by zero raises a `ZeroDivisionError` exception, which is caught by the `except ZeroDivisionError` block. The error message "Error: Division by zero!" is then printed. Handling exceptions in asynchronous code with `asyncio` involves using the `try-except` block within the context of an async task. ### Get the Honeybadger Newsletter Join our [community of kick-ass developers](https://www.honeybadger.io/newsletter/) as we learn engineering, DevOps, and cloud architecture. We'll never spam you; we *will* send you cool stuff like exclusive content, memes, and occasional giveaways. ## Developing custom exceptions Python allows software developers to create custom Python exception classes to handle specific types of errors within their applications. Creating custom exception classes provides a novel way to organize and categorize errors, making code more maintainable and enabling more granular error handling. With these benefits in mind, let’s now explore the process of creating custom exception classes in Python. To create a custom exception class, you need to define a new class that inherits from one of the built-in exception classes or the base `Exception` class. Here is a simple example: ``` import logging class CustomException(Exception): def __init__(self, message): super().__init__(message) self.error_code = 1001 def log_error(self): logger = logging.getLogger("custom_logger") logger.error(f"Custom Exception occurred: {self}") def divide(a, b): if b == 0: raise CustomException("Division by zero is not allowed.") return a / b try: result = divide(10, 0) except CustomException as ce: print("Error code:", ce.error_code) ce.log_error() ``` Output: ``` Error code: 1001 Custom Exception occurred: Division by zero is not allowed. ``` In the code above, the `CustomException` class is defined, inheriting from the base `Exception` class. It has an additional attribute, `error_code`, which is set to `1001` in the constructor (`__init__` method). The `super().__init__(message)` call initializes the base `Exception` class with the given error message. The `log_error` method retrieves or creates a logger named "custom\_logger" using `logging.getLogger("custom_logger")`. The `logger.error` method is used to log an error message indicating that a custom exception occurred. The `{self}` expression in the log message includes the string representation of the exception object. The `divide` function performs division between two numbers. If the divisor (`b`) is zero, the `CustomException` is triggered with the error message "Division by zero is not allowed." The division operation is attempted within a `try-except` block. If a `CustomException` is raised, it is caught using the `except CustomException as ce` block. The error code of the exception is printed (`print("Error code:", ce.error_code)`) to demonstrate access to the `error_code` attribute of the `CustomException` instance. Finally, the `log_error` method is invoked (`ce.log_error()`) to log the exception. ## Best practices to keep in mind for Python exception handling Python’s flexibility makes catching and raising exceptions straightforward, but using this carefully is essential. Poorly structured exception handling can make code messy and unpredictable. Following some established standards to keep your exception handling straightforward is in your best interest. Here are a few best practices to keep in mind: ### Don’t use too broad of a `catch` Avoid using overly broad `except Exception:` blocks unless it’s absolutely necessary. Broad catches can mask programming errors (like typos or bad logic) that should surface for debugging. Instead, handle specific exception types whenever possible. ### Log and re-raise exceptions to keep track of them If you’re catching an exception solely to log or perform housekeeping before shutting down, consider re-raising the same exception. This preserves the original traceback and makes debugging easier. If you catch an exception just to log it, you’ll probably have some unexpected application flow. ### Separate exception recovery from business logic Keep your “happy path” business logic decoupled from the fallback behavior. Exceptional cases should *not* be normal. This makes your code cleaner. Leverage the `else` clause on `try` blocks for code that should only run if no exception was raised. ### Document expected failures Good documentation or even simple comments across your codebase can go a long way toward clarifying which exceptions a function can raise. This helps your team (or even your future self) confidently write code without guesswork. ## Logging exceptions to an error monitoring service It’s inevitable that you’ll eventually come across errors and exceptions when developing software applications. Monitoring and managing these Python errors effectively is crucial for maintaining the stability and performance of an application. One effective approach is to log exceptions to an error-monitoring service. Such services provide valuable insights into the occurrence and impact of errors, enabling developers to identify and resolve issues promptly. By integrating an error-monitoring service into the development workflow, teams can enhance their debugging and troubleshooting capabilities, leading to improved user experience and overall application quality. Examples of some error monitoring services are Honeybadger, Sentry, Datadog, etc. ### Integrating Honeybadger for error monitoring [Honeybadger](https://www.honeybadger.io/) is a powerful error-monitoring tool for Python applications. Integrating an error monitoring service like Honeybadger into your development workflow provides numerous benefits for effectively managing exceptions. From real-time notifications and error grouping to rich diagnostics and trend analysis, Honeybadger equips you with the tools you need to quickly identify, investigate, and resolve errors and ultimately enhance the overall quality and reliability of your applications. To show off some of this cool functionality, let’s now explore some features and examples of integrating Honeybadger into your Python code. To use Honeybadger you need to first get an API key, which you get by signing up for [a free Honeybadger account](https://www.honeybadger.io/plans/). On the [Honeybadger projects page](https://app.honeybadger.io/projects), click on the “**Create your first project**” button, give your project a name, and finally click the “**Settings**” tab, and you will be able to access your API key there. You will also need to install Honeybadger locally. You can do that with the following command: ``` pip install honeybadger ``` ### Get Honeybadger's best Python articles in your inbox We publish 1-2 times per month. Subscribe to get our Python articles as soon as we publish them. We'll never spam you; we *will* send you cool stuff like exclusive content, memes, and occasional giveaways. ### Customizing Error Notifications Honeybadger allows you to customize error notifications based on your application’s needs. You can specify the severity level, tags, and additional context to be included in the error reports. Here’s an example: ``` from honeybadger import honeybadger # Configure Honeybadger with your API key honeybadger.configure(api_key='hbp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') # paste your API key def divide(a, b): result = a / b return result # Example usage of the divide function try: result = divide(10, 0) except Exception as e: honeybadger.notify(e, context={'user_id': 123}, error_message='Division by zero is not allowed.') #'Division by zero is not allowed.' will show if `e` was not passed ``` In this example, `honeybadger.notify()` the `context` parameter provides additional contextual information, such as the user ID associated with the error. To manually send an error message to Honeybadger notification, you add the “error\_message argument. The message in `error_message` will show up only when there is no error passed in the `honeybadger.notify()` method. When you run the above code, you will receive in your email a message notifying you of the error, like the message that can be seen below. ![Python exception handling in Honeybadger](https://www.honeybadger.io/images/blog/posts/a-guide-to-exception-handling-in-python/honeybadger-exc.png) ## Handling Python exceptions in the wild Python exception handling is a critical aspect of writing robust and reliable Python code. By mastering various techniques, from nested try-except blocks to creating custom exceptions, you can enhance the reliability of your code. Additionally, error tracking tools like Honeybadger can monitor for exceptions, allowing you to proactively identify and address issues in your Python applications. Editor's note: This post was originally published in August 2023 and has been updated for accuracy. ![Muhammed Ali](https://www-files.honeybadger.io/authors/muhammedali.png) Written by [Muhammed Ali](https://www.honeybadger.io/blog/authors/muhammedali/) Muhammed is a Software Developer with a passion for technical writing and open source contribution. His areas of expertise are full-stack web development and DevOps. [Back to blog](https://www.honeybadger.io/blog/) ### On this page - [Overview](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/) - [What is an exception?](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#what-is-an-exception) - [Nested try-except blocks](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#nested-try-except-blocks) - [Using try-except-else-finally blocks](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#using-try-except-else-finally-blocks) - [Handling multiple exceptions in a single except block](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-multiple-exceptions-in-a-single-except-block) - [Catching and re-raising exceptions](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#catching-and-re-raising-exceptions) - [Handling exceptions in loops](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-exceptions-in-loops) - [Handling exceptions in asynchronous code (asyncio)](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-exceptions-in-asynchronous-code-asyncio) - [Developing custom exceptions](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#developing-custom-exceptions) - [Best practices to keep in mind for Python exception handling](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#best-practices-to-keep-in-mind-for-python-exception-handling) - [Logging exceptions to an error monitoring service](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#logging-exceptions-to-an-error-monitoring-service) - [Handling Python exceptions in the wild](https://www.honeybadger.io/blog/a-guide-to-exception-handling-in-python/#handling-python-exceptions-in-the-wild) ### Try Honeybadger for FREE Error tracking and performance monitoring in one simple interface. [Start free trial](https://app.honeybadger.io/users/sign_up?plan=team) No credit card required [![Honeybadger](https://www.honeybadger.io/_astro/bolt.D0BsYe2K_20GiLi.svg) ![Honeybadger](https://www.honeybadger.io/_astro/bolt-dark.DI2bt8qx_20GiLi.svg)](https://www.honeybadger.io/) ### Product - [Error Tracking](https://www.honeybadger.io/tour/error-tracking/) - [Uptime Monitoring](https://www.honeybadger.io/tour/uptime-monitoring/) - [Status Pages](https://www.honeybadger.io/tour/status-pages/) - [Dashboards](https://www.honeybadger.io/tour/dashboards/) - [Logging & Observability](https://www.honeybadger.io/tour/logging-observability/) - [Cron & Heartbeat Monitoring](https://www.honeybadger.io/tour/cron-job-heartbeat-monitoring/) - [Integrations](https://www.honeybadger.io/integrations/) - [Plans & pricing](https://www.honeybadger.io/plans/) - [Compare](https://www.honeybadger.io/vs/) - [Changelog](https://www.honeybadger.io/changelog/) ### Stacks - [Ruby](https://www.honeybadger.io/for/ruby/) - [JavaScript](https://www.honeybadger.io/for/javascript/) - [Python](https://www.honeybadger.io/for/python/) - [Elixir](https://www.honeybadger.io/for/elixir/) - [PHP](https://www.honeybadger.io/for/php/) - [All languages](https://www.honeybadger.io/platforms/) ### Developers - [Get support](https://www.honeybadger.io/contact/) - [Documentation](https://docs.honeybadger.io/) - [Blog](https://www.honeybadger.io/blog/) - [Newsletter](https://www.honeybadger.io/newsletter/) - [Discord](https://www.honeybadger.io/discord/) - [Status](https://status.honeybadger.io/) ### Company - [Meet the 'Badgers](https://www.honeybadger.io/about/) - [Job openings](https://www.honeybadger.io/careers/) - [Contact us](https://www.honeybadger.io/contact/) - [Brand assets](https://www.honeybadger.io/assets/) - [Security & compliance](https://www.honeybadger.io/security/) © 2026 Honeybadger Industries LLC [Terms](https://www.honeybadger.io/terms/) [Privacy](https://www.honeybadger.io/privacy/)
Readable Markdown
Exceptions can occur for various reasons, such as invalid input, logical errors, file handling issues, network problems, or other exception conditions. Examples of exceptions in Python include `ZeroDivisionError`, `TypeError`, `FileNotFoundError`, and `ValueError`, among others. Exception handling in Python is a crucial aspect of writing robust and reliable code in Python. This tutorial aims to provide a comprehensive understanding of Python exception handling techniques and provide examples of how those techniques can be used practically. By the end of the tutorial, you’ll know how to build reliable programs. Let’s get started\! ## What is an exception? In Python, an exception is an event that occurs during the execution of a program and disrupts the normal flow of the program. It represents an error or an exception condition that the program encounters and cannot handle by itself. Even with a [great testing suite](https://www.honeybadger.io/blog/beginners-guide-to-software-testing-in-python/), Python exceptions can happen and cause problems for users. When an exception occurs, it is "raised" or "thrown" by the Python interpreter. The exception then propagates up the call stack, searching for an exception handler that can catch and handle the exception. If no suitable exception handler is found, the program terminates, and an error message is displayed. Exceptions differ from syntax errors because syntax errors happen when Python translates your code to bytecode. Syntax errors prevent your program from running at all. Exceptions, on the other hand, are considered runtime errors, which can pop up during the normal execution of otherwise valid code. For example, dividing by zero doesn’t break Python’s syntax rules; it’s valid syntax. But it’s a mathematical impossibility, so it raises an exception. Specifically, it raises a `ZeroDivisionError.` Another key thing to remember about exceptions is that they’re not necessarily catastrophic. They’re special Python objects that can be caught and handled if you can foresee them coming. With proper exception handling, your program has the opportunity to respond to unexpected situations like missing files or invalid user input more gracefully. Instead of crashing, your code can log the error, show a user-friendly message, or even use some predetermined fallback logic. This makes exception handling essential for writing robust applications that can withstand a wide range of real-world scenarios. Here’s an example of a `ZeroDivisionError` exception being raised and handled using a `try-except` block: ``` try: result = 10 / 0 # Raises ZeroDivisionError except ZeroDivisionError: print("Error: Division by zero!") ``` In this example, the code within the `try` block raises a `ZeroDivisionError` exception when attempting to divide by zero. The exception is caught by the `except` block, and the specified error message is printed, allowing the program to continue execution instead of abruptly terminating. ## Nested `try-except` blocks Nested try-except blocks provide a way to handle specific exceptions at different levels of code execution. This technique allows you to catch and handle exceptions more precisely based on the context in which they occur. Consider the following example: ``` try: # Outer try block try: # Inner try block file = open("nonexistent_file.txt", "r") content = file.read() file.close() print("File content:", content) except FileNotFoundError: print("Error: File not found!") except: print("Error: Outer exception occurred!") ``` Output: ``` Error: File not found! ``` In this example, the inner Python try block attempts to open a file "nonexistent\_file.txt" in read mode, which doesn’t exist and raises a `FileNotFoundError`. The exception is caught by the inner except block, which prints the error message "Error: File not found!". Since the exception is handled within the inner except block, the outer except block is not executed. However, if the inner except block was not executed, the exception would propagate to the outer except block, and the code within the outer except block would be executed. ## Using `try-except-else-finally` blocks A powerful feature of Python’s exception-handling paradigm is the option to include `else` and `finally` clauses alongside `try-except`. This structure provides a clear separation of concerns for different parts of your code, making it more readable and robust: ``` def safe_divide(a, b): try: result = a / b except ZeroDivisionError: print("Please don't divide by zero") else: print("Thank you for dividing") finally: print("This happens all the time") safe_divide(10, 0) safe_divide(20, 2) ``` The `try` block is the primary block where you attempt to execute code that might throw an exception. In our example, that’s a division operation. The `except` block catches any exceptions that occur within the try block. In this particular example, that’s a `ZeroDivisionError`. The `else` block executes only if no exceptions are raised in the try block. It’s a good place for code that only runs once everything has succeeded. The `finally` block executes regardless of whether an exception occurred; it is commonly used for cleanup actions like closing files, releasing locks, or cleaning up resources. This structured flow helps you handle errors more precisely, keeps the “happy path” logic separate, and ensures that any necessary teardown always happens. ## Handling multiple exceptions in a single except block Sometimes, you might want to catch multiple types of exceptions, but you want to treat them the same. You can catch multiple exceptions with identical handling logic. Python lets you group them in a single except clause using parentheses. Check out this example: ``` def fetch_data(source): if source == "file": raise FileNotFoundError("File source missing!") elif source == "api": raise ConnectionError("Failed to connect to API!") else: return "Data fetched successfully!" sources = ["file", "api", "database"] for src in sources: try: data = fetch_data(src) print(data) except (FileNotFoundError, ConnectionError) as e: print("Recoverable error:", e) except Exception as ex: print("Unknown error:", ex) ``` In this snippet, you’ll notice a few things. First, both the `FileNotFoundError` and the `ConnectionError` are handled in the same way. Second, you’ll notice that the `except Exception` acts as a fallback if some other unanticipated exception type arises. Grouping exceptions is particularly helpful when you know that your remediation steps for certain errors will be identical. This approach prevents repetitive code and keeps your error handling clear and concise. ## Catching and re-raising exceptions Catching and re-raising exceptions is a useful technique when you need to handle an exception at a specific level of code execution, perform certain actions, and then allow the exception to reproduce to higher levels for further handling. Let’s explore the example further and discuss its significance. In the provided code snippet, the `validate_age` function takes an `age` parameter and checks if it is negative. If the age is negative, a `ValueError` is raised using the `raise` keyword. The exception is then caught by the except block that specifies `ValueError` as the exception type. ``` def validate_age(age): try: if age < 0: raise ValueError("Age cannot be negative!") except ValueError as ve: print("Error:", ve) raise # Re-raise the exception try: validate_age(-5) except ValueError: print("Caught the re-raised exception!") ``` In this case, if the age provided to `validate_age` is -5, the condition `if age < 0` is satisfied, and a `ValueError` is raised with the message "Age cannot be negative!". The except block then catches the `ValueError` and prints the error message using `print("Error:", ve)`. This step allows you to perform specific actions, such as [logging the error](https://www.honeybadger.io/blog/python-logging/) or displaying a user-friendly error message. After printing the error message, the `raise` statement is used to re-raise the caught exception. This re-raised exception propagates to a higher level of code execution, allowing it to be caught by an outer exception handler if present. The output of this code snippet is: ``` Error: Age cannot be negative! Caught the re-raised exception! ``` This example demonstrates the importance of catching and re-raising exceptions. By catching an exception, performing necessary actions, and re-raising it, you have more control over how the exception is handled at different levels of your code. ## Handling exceptions in loops Handling exceptions in loops is essential to ensure the smooth execution of code and prevent premature termination. By incorporating exception handling within loops, you can gracefully handle specific exceptions and continue the loop iteration. Let’s explore the example further to understand its significance. ``` numbers = [1, 2, 3, 0, 4, 5] for num in numbers: try: result = 10 / num print(f"Result: {result}") except ZeroDivisionError: print("Error: Division by zero!") except Exception as e: print("Unknown Error:", e) ``` In this code snippet, we have a loop that iterates over a list of numbers. For each number, a division operation is performed by dividing `10` by the current number. If the number is zero, a `ZeroDivisionError` is raised. The first `except ZeroDivisionError` block catches this exception and prints the error message "Error: Division by zero!". Additionally, there is a generic `except Exception as e` block to catch any other exceptions that may occur during the loop iteration. The output of this code snippet is: ``` Result: 10.0 Result: 5.0 Result: 3.3333333333333335 Error: Division by zero! Result: 2.5 Result: 2.0 ``` As we can see from the output, the loop successfully performs the division operation for non-zero numbers and prints the results. When encountering a zero, the `ZeroDivisionError` is caught, and the error message is printed. The loop continues to execute, handling each iteration gracefully, even in the presence of exceptions. ## Handling exceptions in asynchronous code (`asyncio`) Handling exceptions in asynchronous code can be crucial to ensure the stability and proper functioning of asynchronous tasks. The **`asyncio`** library in Python provides tools and mechanisms to handle exceptions in async tasks effectively. ``` import asyncio async def divide(a, b): return a / b async def main(): try: await divide(10, 0) except ZeroDivisionError: print("Error: Division by zero!") asyncio.run(main()) ``` In this code snippet, we have an asynchronous function named `divide` that performs a division operation on two numbers. The division is executed using the division operator `/`, which may raise a `ZeroDivisionError` if the denominator is zero. Within this function, we use the `try-except` block to catch any `ZeroDivisionError` that may occur during the execution of the `divide` coroutine. The output of this code snippet is: ``` Error: Division by zero! ``` As expected, the division by zero raises a `ZeroDivisionError` exception, which is caught by the `except ZeroDivisionError` block. The error message "Error: Division by zero!" is then printed. Handling exceptions in asynchronous code with `asyncio` involves using the `try-except` block within the context of an async task. ### Get the Honeybadger Newsletter Join our [community of kick-ass developers](https://www.honeybadger.io/newsletter/) as we learn engineering, DevOps, and cloud architecture. We'll never spam you; we *will* send you cool stuff like exclusive content, memes, and occasional giveaways. ## Developing custom exceptions Python allows software developers to create custom Python exception classes to handle specific types of errors within their applications. Creating custom exception classes provides a novel way to organize and categorize errors, making code more maintainable and enabling more granular error handling. With these benefits in mind, let’s now explore the process of creating custom exception classes in Python. To create a custom exception class, you need to define a new class that inherits from one of the built-in exception classes or the base `Exception` class. Here is a simple example: ``` import logging class CustomException(Exception): def __init__(self, message): super().__init__(message) self.error_code = 1001 def log_error(self): logger = logging.getLogger("custom_logger") logger.error(f"Custom Exception occurred: {self}") def divide(a, b): if b == 0: raise CustomException("Division by zero is not allowed.") return a / b try: result = divide(10, 0) except CustomException as ce: print("Error code:", ce.error_code) ce.log_error() ``` Output: ``` Error code: 1001 Custom Exception occurred: Division by zero is not allowed. ``` In the code above, the `CustomException` class is defined, inheriting from the base `Exception` class. It has an additional attribute, `error_code`, which is set to `1001` in the constructor (`__init__` method). The `super().__init__(message)` call initializes the base `Exception` class with the given error message. The `log_error` method retrieves or creates a logger named "custom\_logger" using `logging.getLogger("custom_logger")`. The `logger.error` method is used to log an error message indicating that a custom exception occurred. The `{self}` expression in the log message includes the string representation of the exception object. The `divide` function performs division between two numbers. If the divisor (`b`) is zero, the `CustomException` is triggered with the error message "Division by zero is not allowed." The division operation is attempted within a `try-except` block. If a `CustomException` is raised, it is caught using the `except CustomException as ce` block. The error code of the exception is printed (`print("Error code:", ce.error_code)`) to demonstrate access to the `error_code` attribute of the `CustomException` instance. Finally, the `log_error` method is invoked (`ce.log_error()`) to log the exception. ## Best practices to keep in mind for Python exception handling Python’s flexibility makes catching and raising exceptions straightforward, but using this carefully is essential. Poorly structured exception handling can make code messy and unpredictable. Following some established standards to keep your exception handling straightforward is in your best interest. Here are a few best practices to keep in mind: ### Don’t use too broad of a `catch` Avoid using overly broad `except Exception:` blocks unless it’s absolutely necessary. Broad catches can mask programming errors (like typos or bad logic) that should surface for debugging. Instead, handle specific exception types whenever possible. ### Log and re-raise exceptions to keep track of them If you’re catching an exception solely to log or perform housekeeping before shutting down, consider re-raising the same exception. This preserves the original traceback and makes debugging easier. If you catch an exception just to log it, you’ll probably have some unexpected application flow. ### Separate exception recovery from business logic Keep your “happy path” business logic decoupled from the fallback behavior. Exceptional cases should *not* be normal. This makes your code cleaner. Leverage the `else` clause on `try` blocks for code that should only run if no exception was raised. ### Document expected failures Good documentation or even simple comments across your codebase can go a long way toward clarifying which exceptions a function can raise. This helps your team (or even your future self) confidently write code without guesswork. ## Logging exceptions to an error monitoring service It’s inevitable that you’ll eventually come across errors and exceptions when developing software applications. Monitoring and managing these Python errors effectively is crucial for maintaining the stability and performance of an application. One effective approach is to log exceptions to an error-monitoring service. Such services provide valuable insights into the occurrence and impact of errors, enabling developers to identify and resolve issues promptly. By integrating an error-monitoring service into the development workflow, teams can enhance their debugging and troubleshooting capabilities, leading to improved user experience and overall application quality. Examples of some error monitoring services are Honeybadger, Sentry, Datadog, etc. ### Integrating Honeybadger for error monitoring [Honeybadger](https://www.honeybadger.io/) is a powerful error-monitoring tool for Python applications. Integrating an error monitoring service like Honeybadger into your development workflow provides numerous benefits for effectively managing exceptions. From real-time notifications and error grouping to rich diagnostics and trend analysis, Honeybadger equips you with the tools you need to quickly identify, investigate, and resolve errors and ultimately enhance the overall quality and reliability of your applications. To show off some of this cool functionality, let’s now explore some features and examples of integrating Honeybadger into your Python code. To use Honeybadger you need to first get an API key, which you get by signing up for [a free Honeybadger account](https://www.honeybadger.io/plans/). On the [Honeybadger projects page](https://app.honeybadger.io/projects), click on the “**Create your first project**” button, give your project a name, and finally click the “**Settings**” tab, and you will be able to access your API key there. You will also need to install Honeybadger locally. You can do that with the following command: ``` pip install honeybadger ``` ### Get Honeybadger's best Python articles in your inbox We publish 1-2 times per month. Subscribe to get our Python articles as soon as we publish them. We'll never spam you; we *will* send you cool stuff like exclusive content, memes, and occasional giveaways. ### Customizing Error Notifications Honeybadger allows you to customize error notifications based on your application’s needs. You can specify the severity level, tags, and additional context to be included in the error reports. Here’s an example: ``` from honeybadger import honeybadger # Configure Honeybadger with your API key honeybadger.configure(api_key='hbp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') # paste your API key def divide(a, b): result = a / b return result # Example usage of the divide function try: result = divide(10, 0) except Exception as e: honeybadger.notify(e, context={'user_id': 123}, error_message='Division by zero is not allowed.') #'Division by zero is not allowed.' will show if `e` was not passed ``` In this example, `honeybadger.notify()` the `context` parameter provides additional contextual information, such as the user ID associated with the error. To manually send an error message to Honeybadger notification, you add the “error\_message argument. The message in `error_message` will show up only when there is no error passed in the `honeybadger.notify()` method. When you run the above code, you will receive in your email a message notifying you of the error, like the message that can be seen below. ![Python exception handling in Honeybadger](https://www.honeybadger.io/images/blog/posts/a-guide-to-exception-handling-in-python/honeybadger-exc.png) ## Handling Python exceptions in the wild Python exception handling is a critical aspect of writing robust and reliable Python code. By mastering various techniques, from nested try-except blocks to creating custom exceptions, you can enhance the reliability of your code. Additionally, error tracking tools like Honeybadger can monitor for exceptions, allowing you to proactively identify and address issues in your Python applications. Editor's note: This post was originally published in August 2023 and has been updated for accuracy.
Shard197 (laksa)
Root Hash15249212189560886397
Unparsed URLio,honeybadger!www,/blog/a-guide-to-exception-handling-in-python/ s443