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.
Python errors as values: Comparing useful patterns from Rust and Go
61–70 of 77 posts
Re: Python errors as values: Comparing useful patterns from Rust and Go
#62I don't understand why people insist that all errors must be handled all the time. It could be my C++ background, but I feel there are two very different errors: Expected errors - like "user not found" - should use a value instead of exception. In Python, you can use sentinel objects, or tuples, or None.. lots of options really. Occasionally there is a good reason to use exceptions for flow control even for know erro…
That's why exceptions are called exceptions, not errors. If a routine called openFile() can't open a file, that's a pretty exceptional situation, and it's up to the caller to decide whether the exact reason is an error in their case.
The exception object is already a value that can have not only a message text, but also any data members, so why reinvent the wheel with sentinel objects, tuples, etc.?
> The examples in post, where you catch exception and re-raise Exception back are terrible - what's the point of them? There is no extra clarity, just verbosity. I would defect them in any code review.
Typically, you re-raise an exception after adding some content to it. This may be less important in Python, which gives you a great stack trace, but in a language like C++, the lack of context information makes the exception basically useless.
Re: Python errors as values: Comparing useful patterns from Rust and Go
#63Quite 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…
I think it's kinda the opposite actually... with go, you have to go out of the way to generate those errors, while in Python, most of this is automatic. In particular, the ReadFile example in Python will raise OSError, and those already include filename and error message. So you'd get the same result with 0 extra lines. For the second example, the json unmarshalling will not auto-add filename, but its easy enough to…
I accept it's largely personal preference, but having used both mechanisms for many years, I find Go best practices for error handling are simple and easy to follow, and results in easily maintainable code, compared to exception handling which doesn't really come with a simple set of best practices, meaning it is often badly put together or added as an afterthought.
Re: Python errors as values: Comparing useful patterns from Rust and Go
#64Earlier quoted context omitted.
We write wrappers for standard and third-party libraries that throw errors. For example, we have our own dump_json function that catches-and-returns errors thrown by json.dumps. We didn't need many wrappers given the nature of our SDK, but some programs will need many wrappers and that could get unwieldy
But people are going to import your SDK, so your customers will be stuck with a mix of thrown exceptions and errors-as-values in their code. Based on your decision to write wrappers internally, presumably you'd argue that the sensible thing for them to do is to write wrappers either for your SDK or for every other library to get back to a consistent style and less mental overhead.
We don't return errors in our public methods so our SDK still feels Pythonic. We're debating whether to add `_safe` suffix methods that return errors to give people the option, but for now our library only throws errors to consumers.
You might ask "well what's the point if you still throw errors to consumers?" We feel that forcing our engineers to deal with errors where they can happen is worthwhile. Our team writes a lot of Go and we love how it forces you to think about every spot that can error, so we wanted the same experience in Python
Re: Python errors as values: Comparing useful patterns from Rust and Go
#65> 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
#66Re: Python errors as values: Comparing useful patterns from Rust and Go
#67Oh, 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
#68Earlier quoted context omitted.
But people are going to import your SDK, so your customers will be stuck with a mix of thrown exceptions and errors-as-values in their code. Based on your decision to write wrappers internally, presumably you'd argue that the sensible thing for them to do is to write wrappers either for your SDK or for every other library to get back to a consistent style and less mental overhead.
Author here! We don't return errors in our public methods so our SDK still feels Pythonic. We're debating whether to add `_safe` suffix methods that return errors to give people the option, but for now our library only throws errors to consumers. You might ask "well what's the point if you still throw errors to consumers?" We feel that forcing our engineers to deal with errors where they can happen is worthwhile. Our…
I might express a slight preference that your internal code is idiomatic if I'm going to be poking around in there at some point. But I wouldn't be bikeshedding about your C coding style if you'd provided a Python module written in C, so I don't feel I should have a strong opinion as a user here.
Re: Python errors as values: Comparing useful patterns from Rust and Go
#69"How we made a non-idiomatic Python SDK for our app that Python devs will hate"
Re: Python errors as values: Comparing useful patterns from Rust and Go
#70Earlier quoted context omitted.
I think it's kinda the opposite actually... with go, you have to go out of the way to generate those errors, while in Python, most of this is automatic. In particular, the ReadFile example in Python will raise OSError, and those already include filename and error message. So you'd get the same result with 0 extra lines. For the second example, the json unmarshalling will not auto-add filename, but its easy enough to…
This is fine as far as it goes. The problem is that the exception can only report context that it knows about. By contrast, the Go version allows you to include any extra context you want, to make debugging easier. Eg, your error might not just include file reading or Json parsing, but which transaction or customer was involved at the time. You can do with exceptions but you either have to try/catch every statement,…
dtodf, err := os.Open(filepath.Join(filepath.Dir(file), "diffid-to-descriptor"))
if err != nil {
return nil, fmt.Errorf("opening diffid-to-descriptor: %w", err)
}
See how they forgot to put filename in the error message? if there is some sort of error with the file, you'll have to resort to strace to find out what the name is... Not to mention that this very function returns json parse errors without context, and the caller "getMeta" calls multiple functions and returns the errors without context as well...best practices are nice, but fully automatic is even better. The minimum-effort path in Python produced vastly more useful traces than in Go, and unfortunately too many programmers go mininum-effort path.
[0] https://github.com/ko-build/ko/blob/cfc13deeb6417d7e1582f031...