Errors in Python

In Python, error handling is typically done using exceptions rather than explicit error return values. However, we can simulate a similar approach to demonstrate the concept. Here’s how we might implement a similar error handling pattern in Python:

from typing import Tuple, Union

# By convention, we'll use a tuple to return both a value and an error
def f(arg: int) -> Tuple[int, Union[str, None]]:
    if arg == 42:
        # We return a tuple with the error value and an error message
        return -1, "can't work with 42"
    # A None value in the error position indicates that there was no error
    return arg + 3, None

# Sentinel errors are typically implemented as custom Exception classes in Python
class OutOfTeaError(Exception):
    pass

class PowerError(Exception):
    pass

def make_tea(arg: int) -> None:
    if arg == 2:
        raise OutOfTeaError("no more tea available")
    elif arg == 4:
        # We can wrap errors by catching and re-raising with additional context
        try:
            raise PowerError("can't boil water")
        except PowerError as e:
            raise Exception(f"making tea: {str(e)}") from e
    return None

def main():
    for i in [7, 42]:
        # It's common to use a try-except block for error handling in Python
        try:
            r, e = f(i)
            if e is not None:
                print(f"f failed: {e}")
            else:
                print(f"f worked: {r}")
        except Exception as e:
            print(f"An unexpected error occurred: {e}")

    for i in range(5):
        try:
            make_tea(i)
            print("Tea is ready!")
        except OutOfTeaError:
            print("We should buy new tea!")
        except PowerError:
            print("Now it is dark.")
        except Exception as e:
            print(f"unknown error: {str(e)}")

if __name__ == "__main__":
    main()

In this Python version:

  1. We use tuples to simulate Go’s multiple return values for errors.

  2. Instead of a built-in error interface, we use None to indicate no error and a string (or custom Exception) for error messages.

  3. Sentinel errors are implemented as custom Exception classes.

  4. We use exceptions and try-except blocks for error handling, which is more idiomatic in Python.

  5. Error wrapping is simulated by catching and re-raising exceptions with additional context.

  6. The errors.Is() functionality is replaced with exception type checking in except clauses.

When you run this program, you should see output similar to:

f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

This example demonstrates how to implement a similar error handling pattern in Python, although it’s worth noting that this is not typically how errors are handled in idiomatic Python code. Python generally favors a “ask for forgiveness, not permission” approach using try-except blocks for error handling.