DEV Community

Cover image for Dead Simple Python: Errors

Dead Simple Python: Errors

Jason C. McDonald on January 27, 2019

Like the articles? Buy the book! Dead Simple Python by Jason C. McDonald is available from No Starch Press. Exceptions. One of the arch-nemese...
Collapse
 
rhymes profile image
rhymes

Very informative, well done! raise from is one of my favorite features, it makes tracebacks so much better.

I don't think I've recently used the else clause in exception handling but thanks for reminding me it's there :D

Collapse
 
rpalo profile image
Ryan Palo

This is great! Thank you so much for the detailed walkthrough!

Question: I had never seen the super syntax used when defining custom exceptions. I've always just defined them as empty classes inheriting from a parent class (either Exception or one of my other custom exceptions.

When I do that, I get to see the message, but when I pass-through the message using the __init__ method like you've got shown, it looks different.

Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:57:15) [MSC v.1915 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: class GloopException(Exception):
   ...:     pass
   ...:
   ...:

In [2]: raise GloopException("Dilly Dilly")
---------------------------------------------------------------------------
GloopException                            Traceback (most recent call last)
<ipython-input-2-ed1926277ce6> in <module>()
----> 1 raise GloopException("Dilly Dilly")

GloopException: Dilly Dilly

In [3]: class Gloop2Exception(Exception):
   ...:     def __init__(self, message):
   ...:         super().__init__(self, message)
   ...:

In [4]: raise Gloop2Exception("Hwaaaa")
---------------------------------------------------------------------------
Gloop2Exception                           Traceback (most recent call last)
<ipython-input-4-1b3205a8599c> in <module>()
----> 1 raise Gloop2Exception("Hwaaaa")

Gloop2Exception: (Gloop2Exception(...), 'Hwaaaa')

It seems almost better to just declare an empty class with pass to me. Is that wrong? Are there downsides? The official docs aren't super clear as to what's more common/expected.

Collapse
 
codemouse92 profile image
Jason C. McDonald

I'd be curious how this behaves in terms of actually catching the exception with try...except, and what information would be available to e in except GloopException as e:? To be honest, I really don't know; I only understand that the standard is to use super().

I'll ask the Python friends who have been editing this series if they have any insight. ;)

Collapse
 
codemouse92 profile image
Jason C. McDonald • Edited

Okay, so after a bit of discussion in #python on Freenode IRC, we've come to this verdict:

  1. Any time you have an __init__() and you've inherited, call super().__init__(). Period. However...

  2. There are some camps that argue that if you don't otherwise need an __init__() (other than to call super), don't bother with it at all. Others, including myself, believe in always explicitly declaring an initializer with super(). I don't know that there's a clear right or wrong here. They both work out the same.

  3. The weird behavior in your code is because there's a typo...which honestly came from me. You should NOT pass self to super().__init__(self, message)! That should instead read super().__init__(message). (I've fixed that in my article.)

Thanks to altendky, dude-x, _habnabit, TML, and Yhg1s of Freenode IRC's #python for the insight.

Thread Thread
 
rpalo profile image
Ryan Palo

Ooooohkay, gotcha. That makes total sense. Yeah, I think I fall in the camp of not showing the __init__ unless I'm extending it (because I think it looks prettier), but I could see the "explicit is better" argument for the other way.

The most important thing is that you showed me the right way to extend if I need to, which I really appreciate!

Collapse
 
rpalo profile image
Ryan Palo

Yeah, definitely. Good to know what is standard to use. Thanks so much! :)

Thread Thread
 
codemouse92 profile image
Jason C. McDonald

Incidentally, I'm trying to find out more, because there seems to be some debate! (It's Python, so of course there is.)

Collapse
 
ardunster profile image
Anna R Dunster • Edited

Great article. Would not have guessed that try...except would be more efficient than if/else in cases like you mentioned, although that makes sense.

Any idea what the logic is for having finally: run regardless of returns?

Great info about "except WhateverError as e:" and "raise from".

Edit: Also always great reading the informative comments, both interesting questions and answers!

Collapse
 
codemouse92 profile image
Jason C. McDonald

finally always runs to ensure cleanup/teardown behavior runs. It comes in handy surprisingly often.

Also, whether try or if is more efficient is really really subjective. If it matters, always measure.

Collapse
 
ardunster profile image
Anna R Dunster

Good point. But good to even be aware of the idea!

Collapse
 
codemouse92 profile image
Jason C. McDonald

I can see how that would be confusing, but in general, it doesn't matter either way. Switching it like you suggested would change the Calculation Result: line, but it wouldn't change the actual function return which is printed out separately.

If you swapped those, you'd see:

Calculation Result: -1
2.0

The important part is that second line; that's printed from the function's return.

Collapse
 
aezore profile image
Jose Rodriguez

Really informative guide, I don't use try//except as often as I should but definitely will "try" to be more efficient and safe.

Also, English is not my mother tongue, but I was wondering if you might have a typo in "finally is running, even after our return statement. The function doesn't exist like it normally would." as in "exits" instead of "exist" ? It makes more sense to me.

Cheers, mate ♥

Collapse
 
codemouse92 profile image
Jason C. McDonald

You are absolutely right! I'm going to fix that now. Great catch.

Collapse
 
sandordargo profile image
Sandor Dargo

Thanks for the article. As I'm coming from the C++ world, I'm suspicious when people say that try-except blocks are not so expensive.

And that's true, relatively they are not as expensive as in C++.

But have you actually measured, for example, the cost of an extra key check in a dictionary lookup compared to raising an exception?

This would vary from machine to machine, I guess, but on mine, the failure case is 30-40x slower with the try-raise block compared to the if-else. That is quite significant.

On the other hand, the success case is about 30-50% faster with the try-case block.

30-50% compared to 3000-4000%.

To me, the bottom line is, if performance matters, measure and know your data. Otherwise, you might just mess things up.

Collapse
 
codemouse92 profile image
Jason C. McDonald • Edited

It's a good point - you should always measure for your specific scenario.

The except is supposed to be the "exceptional" scenario, rather than the rule. In general, the try should succeed several times more often than it fails. Contrast that with the if...else (ask permission) scenario, where we perform the lookup every time, the try...except scenario actually winds up being the more efficient approach in most cases. (See this StackOverflow answer.)

To put that another way, if you imagine that just the if...else and try...except structures by themselves are roughly identical in performance, at least typical scenarios, it is the conditional statement inside of the if() that is the point of inefficiency.

I'm oversimplifying, of course, but this can at least get you in the ballpark. if(i % 2 == 0) is going to be pretty inexpensive, and would succeed only 50% of the time in any given sequential, so that would be a case where we'd almost certainly use an if...else for our flow control; try...except would be too expensive anyway! By contrast, the dictionary lookup is quite a bit more expensive, especially if it succeeds eight times more than it fails. If we know from our data that it will fail more than succeed, however, an "ask permission" approach may indeed be superior.

At any rate, yes, measure, and consider the costs of your particular scenario.

P.S. As I mentioned in the article, of course, the collections.defaultdict would be considered superior to both try...except and if...else in the example scenario.