3 ms·
Yes. The Python modules I write generally have: class Error(Exception): pass class FooError(Error): … and then, in the generic case: try:
by teddyh 4y ago
Yes. The Python modules I write generally have:
class Error(Exception):
pass
class FooError(Error):
…
and then, in the generic case:
try:
failing_external_function()
except Exception as e:
raise Error(*e.args) from e
(And if the code encounters a foo situation, it explicitly raises FooError, possibly with extra parameters, etc.)
It is really annoying to have to catch all of, say, socket.error, SSL errors, FileNotFound, etc. etc. in every call to some function in a module, if you use that module extensively. It’s much easier to just catch module.Error and be done with it. If you need to handle foo errors specially, you catch FooError.
- hbrn 4y agoWhat happens if function from another module calls failing_external_function()? Will you wrap it in another_module.Error? This sounds like viral boilerplate: any function that can fail forces all other functions in the stack trace to be wrapped. Seems quite unpythonic, the whole point of exceptions is to avoid this boilerplate by propagating. Are you just writing boilerplate to assign exceptions to modules? But this information is already present in the stack trace, what is the point? It's too generic to actually handle exceptions, and too redundant to provide debugging value.
- teddyh 4y ago> What happens if function from another module calls failing_external_function()? Will you wrap it in another_module.Error? Yes? If you call a function in another module, generally you’ll always have to catch and handle errors specific to that module. If that module does not wrap its errors (like socket.error, FileNotFound, etc.), you’ll have to handle those, as well. And, like Parthenon points out, if that module ever changes its internal implementation, it will suddenly raise different errors, which your code does not catch. On the other hand, if the module wraps its errors, you’ll have a guarantee that you’ll be able to catch them. I don’t know why you keep bringing up debugging; Python’s “raise from” keeps the original exception intact and available. > Are you just writing boilerplate to assign exceptions to modules? Wait, are you talking about monkey-patching? I’m not doing that. I’m talking about the case where you write your own module for something, and then use that module in another program (possibly itself a module).
- hbrn 4y agoYour code example essentially renames a regular wildcard Exception to module-specific wildcard exception. But here's a thing: For direct callers of your module, regular wildcard and module wildcard are exactly the same. There's absolutely no difference for them whether they are catching module.Error or Exception, because your code is structured so that mean exactly the same thing. For indirect callers higher in the stack, your custom wildcard exception makes error handling harder, because instead of familiar exceptions they will see your custom one. Your wrapper makes it harder to access what happened, but easier to access where it happened. This is just a bad tradeoff. In some cases for you where might be more valuable than what. But you are in no position to assume that this is how it's going to be for your customers. Examples where this practice might be useful (e.g. explicitly shifting the blame to a 3rd party) are simply too rare to justify those wrappers. You could still achieve the same results by inspecting the stack, without impeding the ability of your customers to deal with known exceptions.
- teddyh 4y ago> For direct callers of your module, regular wildcard and module wildcard are exactly the same. Well, no. I usually only wrap exceptions which I know to possibly expect, and only wrap ‘Exception’ in code sections where I know I want to catch any exception, no matter what (which is rare, but happens). Unexpected exceptions (either of an unexpected type or in an unexpected place) will still propagate upwards. This allows the code calling my module to catch any reasonably expected exceptions (since I will wrap them in my module.Error, or, really, a more specifc exception class inhereting from module.Error), while still allowing unexpected exceptions to be shown. I realize that I was unclear in my initial description; I do not catch ‘Exception’ all the time, but only occasionally. I most often list the exceptions which I know that the called function could raise in failure states which I know how to handle. > Your wrapper makes it harder to access what happened How? With “raise from”, Python shows you not only the stack trace of the error, but also the stack trace of the original exception, IIRC.
- hbrn 4y ago> How? With “raise from”, Python shows you not only the stack trace of the error, but also the stack trace of the original exception, IIRC. Because you can't catch the original exception, you're stuck with weird module.Error which is too generic to do something about it. You would have to catch the module.Error and then look at e.__cause__ to actually handle the exception. So you end up with the exact same problem Parthenon is talking about, but with extra steps. And pray no other dependencies are following your practice, because you would then have to go into e.__cause__.__cause__ and so on. What exactly is the benefit that module.Error provides to your users compared to letting the original exception propagate?