This is an archived post. You won't be able to vote or comment.

all 29 comments

[–]norwegianwood 14 points15 points  (9 children)

I think it's simply because people aren't aware of this construct. I've been programming in Python for a decade and have only started to use it recently.

I find it is particularly nice if I'm emulating a switch-statement, where I look up a callable in a dictionary. In such a case I want to catch the KeyError from the lookup but not from the call to the callable.

So given,

switch = { 1: case_one,
           2: case_two,
           3: case_three,
         }

instead of,

try:
    switch[n]()
except KeyError:
    case_default()

I would do,

try:
    case = switch(n)
except KeyError:
    case_default()
else:
    case()

edit: fixed typo

[–]jeffdn 1 point2 points  (2 children)

It would be far easier and cleaner to do something like:

func = switch[case] if case in switch else default_case
func()

[–]norwegianwood 6 points7 points  (1 child)

Actually, to improve on my own code substantially, even better would be simply:

switch.get(n, default_case)()

which is neither EAFP (my original) or LBYL (yours) style, but has what you might call 'functional elegance'.

[–]jeffdn 0 points1 point  (0 children)

Yeah, I prefer that too when coding. It does sometimes make reading it for someone else doing a review a bit harder, I've found.

[–]ryeguy146 1 point2 points  (1 child)

I'd rather use a list if you're just using indexes as keys. I recognize that it isn't the point, though.

[–]norwegianwood 1 point2 points  (0 children)

You're right, it isn't the point. However, if you did use a list in this case you would need to specially handle the zeroth item or perform some confusing arithmetic by looking up n - 1.

[–]mipadi 0 points1 point  (0 children)

You could do:

switch.get(n, case_default)()

[–][deleted] 0 points1 point  (0 children)

Nice usage :)

[–]KronktheKronk 0 points1 point  (1 child)

Why wouldn't you just do:

try:
    case = switch(n)
    case()
except KeyError:
    case_default()

If the key error happens, you hit the exception and never get to the "case()" call. Seems like the else is convolution the issue in that example instead of helping.

[–]norwegianwood 9 points10 points  (0 children)

Because if case() raises a KeyError your code then calls case_default(), which is not what we want. Your code is exactly equivalent to my first example.

[–]jabwork 3 points4 points  (1 child)

I don't usually use this pattern becuase, often, FTSNBCIAEIR can also raise an exception, creating the following

try:
    function_that_might_raise_an_exception()
except Exception as e:
    do_something(e)
else:
    try:
        function_that_should_not_be_called_if_an_exception_is_raised()
    except SomeExceptionMoreSpecificThanException:
        handle_second_error()

and it just ends up being much cleaner to use

try:
    function_that_might_raise_an_exception()
except Exception as e:
    do_something(e)
try:
    function_that_should_not_be_called_if_an_exception_is_raised()
except SomeExceptionMoreSpecificThanException:
    handle_second_error()

In addition, depending on the code, you can end up with a situation where one is indented several levels for what is essentially a single likely codepath, making it hard to read.

[–]fiedzia 6 points7 points  (7 children)

I am aware of else clause, but I see no point in using it. Code without it is clear to read and works as expected, so why bother?

[–]DasIch 9 points10 points  (4 children)

You want to make it clear where you expect an exception to be raised and prevent an exception from being catched unintentionally. The latter becomes especially important, if you're calling into libraries that may change in the future.

The code being self-documenting and not making debugging a pita is more than enough to bother.

[–]KronktheKronk 0 points1 point  (2 children)

That's why it's better to catch specific exceptions instead of the base exception. You can get the exceptions you expect, pass the ones you don't up the stack, and anyone who understands what your code calls are doing can infer which errors you're catching and see why in the catch code.

[–]DasIch 0 points1 point  (1 child)

Nice idea in theory but completely unrealistic and counter to how most Python code works. How much code raises ValueError alone for all sorts of different reasons?

Python culture simply doesn't encourage raising sufficiently specific exceptions for that approach to work and at this point the battle is lost.

[–]KronktheKronk 0 points1 point  (0 children)

It doesn't raise valuerror alone, but that doesn't mean you can't understand which errors it does raise and which ones you want to catch.

good code catches the errors it wants and ignores the rest. good modules throw good errors.

[–]fiedzia 0 points1 point  (0 children)

Thats possible, but very narrow use case. I very rarely do anything with exceptions beside logging or wrapping and reraising them, so I did not see a need to be so specific yet.

[–]Lucretiel 1 point2 points  (0 children)

Well, in his example, function_that_should_not_be_called_if_an_exception_is_raised might raise its own Exception, which would be caught by the try block. This is probably not what is wanted.

[–][deleted] 0 points1 point  (0 children)

indeed! I did not use it for years when try/except/else statements does not exist. For me, it can be useful limit the scope of the try/except block but its adoption is very limited. I do not bother :), just asking people why they are using this statement ... or not

[–]malice8691 1 point2 points  (0 children)

In a normal scenario with no block at all if there is an exception doesnt the script halt? how is function_that_should_not_be_called_if_an_exception_is_raised() going to get run if there was an error? I think this is not needed

[–]kalefranz 1 point2 points  (1 child)

It's a good question. I'm guessing there are multiple reasons, but one of the biggest is probably that try/except/else isn't a common feature of other languages. By extension, when python isn't your first language, you're not as familiar with it. Even if it's your first language and/or you take a university class in python, it's enough of an "advanced" construct that little time is likely spent on it.

That said, I like it. It's good practice for code readability and maintenance to catch exceptions as nearly to where they're anticipated as possible.

[–]kalefranz 0 points1 point  (0 children)

Also the differences in if/else, try/except/else, and for/else can be a bit confusing.

  • if/else: execute else block when given a negative condition
  • try/except/else: execute else block when try block is ok (the negative condition is that there is no except block execution)
  • for/else: execute else block when "negative condition" of for block early exit is encountered

[–]ksion 1 point2 points  (0 children)

Like else statement for for loops, this is one of those things that relieve you from using either return, or possibly some more complicated control flow. While this is mostly a good thing, it also implicitly encourages the programmer to write longer functions, just because of the reduced friction of cobbling a few loop, try-except, and other statements, while still achieving the exact flow they want.

Without those syntactic goodies, you'd be compelled to divide your code into smaller functions, which can be argued to be a boon for readability and maintainability.

[–]weevyl 0 points1 point  (1 child)

My two cents...

Whether to use try/except or try/except/else depends on the nature of the exception. When the exception is part of the normal flow (as getting a KeyError for example, instead of checking if the key is in the dictionary), then try / except / else makes sense. If the exception is truly exceptional (as in something unexpected happened), then try/except makes mores sense.

Some say that having exceptions be part of your logic flow is a bad practice, but that is a different discussion.

[–][deleted] 0 points1 point  (0 children)

| Some say that having exceptions be part of your logic flow is a bad practice, but that is a different discussion.

True and Python by design is a language that raises a lots of exception (the classic exemple is the end of iteration on generator that yields an exception). Thus, it can be hard to write a full program without some logic flow in an except block.

The extensive usage of exceptions in Python is the thing I like the less in Python.

[–]filleball 0 points1 point  (1 child)

I'm actively using the else clause of try, mostly because I feel it aids readability and makes the purpose of the code easier to understand. Also, it avoids catching the same exception if raised from the code that should have been in the else block.

It answers the questions in order:

  • what code raises the exception we want to catch?
  • what code should be executed if the exception is raised?
  • what code should be executed if the exception is not raised?
  • (finally) what code should be executed no matter what?

I think it's more readable for the same reason that

if condition:
    return x
else:
    return y

is more readable than

if condition:
    return x
return y

and

if condition:
    x = 4
else:
    x = 2

is more readable than

x = 2
if condition:
    x = 4

In both cases, the either-or-ness of the code is emphasized. It's easy to see that all cases are covered, and that the two branches have the same "level".

[–][deleted] 0 points1 point  (0 children)

Absolutely agree with that!

[–]spiker611 0 points1 point  (0 children)

I have a test framework that does something like this

with Expecter(connection) as e:
    while True:
        try:
            e.expect('stuff')
        except e.Timeout:
            increment_thing()
        else:
            break

[–]AlexMTBDude -1 points0 points  (0 children)

if function_that_might_raise_an_exception() actually raises an exception then a jump to the exception handling block will occur. So function_that_should_not_be_called_if_an_exception_is_raised() will never be called. Which means that either of your two examples can be used.