Live data from Hacker News

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

inngest.com

51–60 of 77 posts

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

#51

Earlier quoted context omitted.

> 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). Is this not solved by using raise from?

By "context" I mean other data that lived at the time of the error. For example, you may want to update a table row when there's an error but if a thrown error takes you too far down the stack then you might not know the row ID anymore

Then you have built your program incorrectly and not caught the error in a place where you still have context (e. g. `YourProgramException` sub-type that indicates a recoverable error or `Exception` in the case that you're building something where it doesn't matter what the error is, just that an error occurred).

This can also be done with error passing, and is a design failure there too:

    _, ex = perform_batch_operation(on: list_of_data)
    if ex:
        # Oh no, we don't know which entry in the list failed
        # and can't update the appropriate row
        # (This, of course, should be handled in
        # perform_batch_operation, not bubbled up here)

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

#52
Not necessarily advocating for unidiomatic python/code, but you could use a decorator to automatically wrap the function call with a try-catch and package the return value appropriately. Lot less mangling of function bodies that way, just return and raise exceptions like normal. You *could* specify the expected Exception type, but considering the rest of the ecosystem probably won’t be following along with documenting expected exceptions, I assume it wouldn’t be worth it and would be more straightforward that all the exception types in the signatures be the plain vanilla Exception. Would also be super inefficient pre-3.11 but ¯\_(ツ)_/¯

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

#53
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 m…

> If they propagate down the stack then you lose context.

Maybe I’m misunderstanding you, but that’s what the Python stack traceback is for. It works pretty well, I prefer it over JS.

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

#54
post #40

Quite apart from the Python discussion, the author captures why I prefer errors as values (a la Go) to exceptions (a la Java), and I have written both styles for many years. > Regardless of the specific approach, returning errors as values makes us consider all of the places an error could occur. The error scenarios become self-documenting and more thoroughly handled. This is so true. Most Java exception handling is…

Exceptions are a very useful tool. In your example, the main program logic is buried in error handling which makes it more difficult to read the code and the code becomes more complex, leading to more bugs. In many cases, it's preferable to have a single error handler centralized in one place, out of line of the main logic. This makes the code more readable, reduces complexity and duplication.

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

#55
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…

lol the most important thing about writing code is that it works, and works well enough that it puts shekels in my pocket. I'll start worrying about Pythonic code when my accountant does. Until then, we'll keep counting shekels.

yeah, this guy gets it. idiomatic-ness is based on p̵o̵p̵u̵l̵a̵r̵i̵t̵y̵ consensus and only indirectly by technical merit. if someone throws shade at code for being nonpythonic, the code probably isn't bad. if it was they'd have focused on that first.

on the other hand, if someone's writing non-pythonic python because pythonic code doesn't work well for their program, it might be a program they shouldn't be writing in python, which in my experience happens way more often than it should.

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

#57

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.

> Some of us used to wrap php "errors" to convert them into exceptions.

Did exactly that back in the day, this comparison is flawed: PHP errors (and warnings, notices) by default went completely outside the flow of the program, and converting them to exceptions was a means of forcing developers to deck with them right away, sometimes at all.

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

#58

Earlier quoted context omitted.

I just don't see how multiple levels of if err: return nil, err in the call stack makes things any clearer? Or how having many instances of that snippet scattered everywhere makes the code easier to read?

Agreed. It's also objectively worse because you lose the stack trace in the final exception you will display to your user. Enjoy debugging that when a customer sends you an error.

Or you could use a 3rd party error package that’s not a good excuse because the standard don’t have it as a priority

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

#59

> tuple[User | None, Exception | None] tuple[User, None] | tuple[None, Exception]

Yup, don't allow nonsensical states (User+Exception or None+None) to even exist.

The original is more like Go approach which is a big flaw with the language.

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

#60

> 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

That's what makes it good :) Well, that and the fact you have to unpack/destructure the result somehow to get at the value (or the error), so you're forced to handle it.
Post reply on HN