Live data from Hacker News

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

inngest.com

21–30 of 77 posts

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

#21
This doesn’t do much. Many of the stdlib functions will still raise exceptions. Also, just because you’re returning exception from a function doesn’t guarantee that something else won’t raise an error. This isn’t an issue in Go/Rust since errors are treated as values in the core language and you’re forced to treat them so everywhere.

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

#22

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…

> Exceptions are just spooky GOTO and a distance

I couldn't agree more! When you throw exceptions it's unclear where the control flow will go

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

#23
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.

> Oh, good, heavyweight error handling just in time for py3.11's zero-cost exception happy path.

I don’t get it. Languages that use exceptions for all kinds of errors will also use exceptions for routine errors that happen as a matter of course—the happy path is not the overwhelmingly most common branch, and errors are not exceptional. In turn not zero-cost for all but the exceptional case.

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

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

Errors/Exceptions by value isn't that important in isolation, but it makes functional programming styles work a lot better (monads).

I think there's still an argument here that this is just a symptom of trying to force functional paradigms on python, which is happening more and more, which I like but am sometimes worried about how pythonic it is... For a one odd script it works well, but when I want to maintain something and have to manage many dependencies and onboard new devs it becomes quite challenging.

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

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

> Sometimes you want to swallow exceptions and sometimes you don't.

You capture `Exception' in your examples which is a serious anti-pattern in nearly all cases. One of the rare places you'd want to do that is at the top level of a service that has to clean up safely before bailing out. None of the contrivances in your example demonstrate that requirement.

If you need to capture an exception, you capture the narrowest one that matches the business/technical requirement you have. The rest should bubble up and get handled by another part of your code or, indeed, cause your app to crash if it's unhandled.

Exceptions don't mean "error" either; they're just exceptions.

> If they propagate down the stack then you lose context

I cannot think of any instances where you 'lose context' by propagating them. You should raise Foo from e if you're re-raising, though, which you clearly did not bother doing. That does preserve context.

Passing an instance of an Exception object around? What is this madness... are you building a debugger? No? A complex framework or tool like pytest where you may want to manipulate the stack frames to benefit the end user? No? Then don't pass exception objects around and raise them somewhere else.

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

#26
post #10

I'm six months into my Python journey. We aren't building a library, so everything runs on 3.11. Having spent most of my career in statically typed and sometimes functional languages, I've found the result package approach and pattern-matching suggestion work well. There's been a suggestion it's not very Pythonic, but I'm willing to continue using a result monad because the trade-off is one-sided; it comfortably pays…

What is a result monad?

It is linguistically related to the (plus, int) monoid; the fact that it conforms to some algebraic/categorical interface is irrelevant in most contexts and just a distraction.

(A “Result” is Rust-speak for a sum type which is either A or B, where (say) B is conventionally an error)

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

#27
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. 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?

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

#28
post #10

I'm six months into my Python journey. We aren't building a library, so everything runs on 3.11. Having spent most of my career in statically typed and sometimes functional languages, I've found the result package approach and pattern-matching suggestion work well. There's been a suggestion it's not very Pythonic, but I'm willing to continue using a result monad because the trade-off is one-sided; it comfortably pays…

What is a result monad?

The result package in the article: https://github.com/rustedpy/result

There's a pretty decent explanation in the readme. If you're more interested in Monads, I am not sure I'd cover that in a HN comment very well, but I would encourage you to take a look.

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

#30

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.

Although I liked the proposal (the `-> User | Exception` idiom is quite beautiful), that's a deal breaker.
Post reply on HN