Live data from Hacker News

Python errors as values: Comparing useful patterns from Rust and Go

inngest.com

11–20 of 77 posts

Re: Python errors as values: Comparing useful patterns from Rust and Go

#11
The most important thing about writing code is that you write it idiomatically for the language it is in. With Python, idiomatic code is known as Pythonic.

  def rename_user(user_id: str, name: str) -> User | Exception:
    # Consume the function
    user = get_user(user_id)
    if isinstance(user, Exception):
        return user
    user.name = name
    return user
This is not Pythonic. Don't do it. Like it or not, Python uses exceptions. How do I know that some other code that get_user calls isn't going to raise an exception? What if the program receives an interrupt signal during the get_user call? When I'm writing Python code, I have to think about exception handling, and now when I use this library, I have to add a bunch of isinstance calls too?

Perspective: I've written primarily in Python for over 20 years, as well as coded extensively in many other languages (C, Objective-C, JavaScript, Java, Bash) and have familiarity with a bunch more (Go, Ruby, Gradle, TCL, Lua, Kotlin, C++).

In practice, exception handling, declared exceptions or not, just isn't that big of a problem. When I'm writing code, I have to reason about it, and exceptions and return/error values are just one aspect of that reasoning.

Re: Python errors as values: Comparing useful patterns from Rust and Go

#12
post #3

Oh, good, heavyweight error handling just in time for py3.11's zero-cost exception happy path. But, more generously: why not simply return an error, and use isinstance(val, Error) for error handling? Making objects and calling functions is quite costly, and that can largely be avoided.

This is what they ended the article on. Return a union type and then error check using isinstance.

Re: Python errors as values: Comparing useful patterns from Rust and Go

#14

One problem I’ve experienced doing something like this is you end up with both exceptions and error values since the standard library and 3rd party libraries are still primarily exception based. You either have to live with it or create wrappers that catch errors and return them as values.

> You either have to live with it or create wrappers that catch errors and return them as values.

Some of us used to wrap php "errors" to convert them into exceptions. Then I switched to Python and was pleased to see the pointless distinction between errors and exceptions gone... not gonna go backwards on this.

Re: Python errors as values: Comparing useful patterns from Rust and Go

#15

I love it. Recently been rewriting Goleko.com backend in Go from Python. The errors as value paradigm is so nice for 90% of the time. It is so much more robust code out of the bat.

> Goleko.com

I won’t claim to know what your intent was but to me the brand placement changed your post from a comment to an ad.

Re: Python errors as values: Comparing useful patterns from Rust and Go

#16
python is where I learned to hate exceptions as control flow - Twisted is twisted. Go was such a breath of fresh air. Now I'm back in python primarily and I am constantly wondering what my functions actually take and actually return. Exceptions are just spooky GOTO and a distance. Our logs are littered with them and have to use Sentry to tell us "oops, you introduced a new error path." Our builds are full of warnings and versioning issues. I've yet to see a decent sized python project that is not a mess.

Re: Python errors as values: Comparing useful patterns from Rust and Go

#17
post #9

Why does the author want to swallow these exceptions? Let them propagate and it's obvious where the issue lies. If you can't handle an exception, don't catch it. > It's impossible to know which line might throw an error without reading the functions themselves... and the functions those functions call... and the functions those functions call. Some thorough engineers may document thrown errors but documentation is un…

Author here!

> Why does the author want to swallow these exceptions?

Sometimes you want to swallow exceptions and sometimes you don't. The examples in the article may be a little contrived, but there are situations where logging an error and continuing is better because it prevents data loss.

> Let them propagate and it's obvious where the issue lies.

If they propagate down the stack then you lose context. You also may not have all the data you need to properly recover (e.g. still write to a table but make the errored field null).

> You still don't know what type of errors a function will throw without inspecting the code and thus how to resolve them.

You're right that we don't know which error occurred with the approach in the article, but you at least know that there could be an error. This is better than try/catch because that doesn't tell you whether an error could happen

Re: Python errors as values: Comparing useful patterns from Rust and Go

#18
post #11

The most important thing about writing code is that you write it idiomatically for the language it is in. With Python, idiomatic code is known as Pythonic. def rename_user(user_id: str, name: str) -> User | Exception: # Consume the function user = get_user(user_id) if isinstance(user, Exception): return user user.name = name return user This is not Pythonic. Don't do it. Like it or not, Python uses exceptions. How do…

This topic is explicitly about considering alternatives to the current Pythonic way.

Re: Python errors as values: Comparing useful patterns from Rust and Go

#19
post #11

The most important thing about writing code is that you write it idiomatically for the language it is in. With Python, idiomatic code is known as Pythonic. def rename_user(user_id: str, name: str) -> User | Exception: # Consume the function user = get_user(user_id) if isinstance(user, Exception): return user user.name = name return user This is not Pythonic. Don't do it. Like it or not, Python uses exceptions. How do…

> What if the program receives an interrupt signal during the get_user call?

You shouldn't catch interrupt signals regardless of whether you're throwing or returning errors. This is why your error classes should extend Exception and not BaseException. The interrupt errors (e.g. KeyboardInterrupt) extend BaseException

Re: Python errors as values: Comparing useful patterns from Rust and Go

#20

> Rust returns returns errors using a "wrapper" type called Result. A Result contains both a non-error value (Ok) and an error value (Err) A `Result` can contain either a non-error value (Result::Ok) or and error value (Result::Err), never both.

Oops! I'll make that correction later today. You're right: they're mutually exclusive
Post reply on HN