Is it a good practice to use try-except-else in Python?

后端 未结 10 1982
情深已故
情深已故 2020-11-22 13:50

From time to time in Python, I see the block:

try:
   try_this(whatever)
except SomeException as exception:
   #Handle exception
else:
   return something


        
相关标签:
10条回答
  • 2020-11-22 13:59

    This is my simple snippet on howto understand try-except-else-finally block in Python:

    def div(a, b):
        try:
            a/b
        except ZeroDivisionError:
            print("Zero Division Error detected")
        else:
            print("No Zero Division Error")
        finally:
            print("Finally the division of %d/%d is done" % (a, b))
    

    Let's try div 1/1:

    div(1, 1)
    No Zero Division Error
    Finally the division of 1/1 is done
    

    Let's try div 1/0

    div(1, 0)
    Zero Division Error detected
    Finally the division of 1/0 is done
    
    0 讨论(0)
  • 2020-11-22 14:00

    What is the reason for the try-except-else to exist?

    A try block allows you to handle an expected error. The except block should only catch exceptions you are prepared to handle. If you handle an unexpected error, your code may do the wrong thing and hide bugs.

    An else clause will execute if there were no errors, and by not executing that code in the try block, you avoid catching an unexpected error. Again, catching an unexpected error can hide bugs.

    Example

    For example:

    try:
        try_this(whatever)
    except SomeException as the_exception:
        handle(the_exception)
    else:
        return something
    

    The "try, except" suite has two optional clauses, else and finally. So it's actually try-except-else-finally.

    else will evaluate only if there is no exception from the try block. It allows us to simplify the more complicated code below:

    no_error = None
    try:
        try_this(whatever)
        no_error = True
    except SomeException as the_exception:
        handle(the_exception)
    if no_error:
        return something
    

    so if we compare an else to the alternative (which might create bugs) we see that it reduces the lines of code and we can have a more readable, maintainable, and less buggy code-base.

    finally

    finally will execute no matter what, even if another line is being evaluated with a return statement.

    Broken down with pseudo-code

    It might help to break this down, in the smallest possible form that demonstrates all features, with comments. Assume this syntactically correct (but not runnable unless the names are defined) pseudo-code is in a function.

    For example:

    try:
        try_this(whatever)
    except SomeException as the_exception:
        handle_SomeException(the_exception)
        # Handle a instance of SomeException or a subclass of it.
    except Exception as the_exception:
        generic_handle(the_exception)
        # Handle any other exception that inherits from Exception
        # - doesn't include GeneratorExit, KeyboardInterrupt, SystemExit
        # Avoid bare `except:`
    else: # there was no exception whatsoever
        return something()
        # if no exception, the "something()" gets evaluated,
        # but the return will not be executed due to the return in the
        # finally block below.
    finally:
        # this block will execute no matter what, even if no exception,
        # after "something" is eval'd but before that value is returned
        # but even if there is an exception.
        # a return here will hijack the return functionality. e.g.:
        return True # hijacks the return in the else clause above
    

    It is true that we could include the code in the else block in the try block instead, where it would run if there were no exceptions, but what if that code itself raises an exception of the kind we're catching? Leaving it in the try block would hide that bug.

    We want to minimize lines of code in the try block to avoid catching exceptions we did not expect, under the principle that if our code fails, we want it to fail loudly. This is a best practice.

    It is my understanding that exceptions are not errors

    In Python, most exceptions are errors.

    We can view the exception hierarchy by using pydoc. For example, in Python 2:

    $ python -m pydoc exceptions
    

    or Python 3:

    $ python -m pydoc builtins
    

    Will give us the hierarchy. We can see that most kinds of Exception are errors, although Python uses some of them for things like ending for loops (StopIteration). This is Python 3's hierarchy:

    BaseException
        Exception
            ArithmeticError
                FloatingPointError
                OverflowError
                ZeroDivisionError
            AssertionError
            AttributeError
            BufferError
            EOFError
            ImportError
                ModuleNotFoundError
            LookupError
                IndexError
                KeyError
            MemoryError
            NameError
                UnboundLocalError
            OSError
                BlockingIOError
                ChildProcessError
                ConnectionError
                    BrokenPipeError
                    ConnectionAbortedError
                    ConnectionRefusedError
                    ConnectionResetError
                FileExistsError
                FileNotFoundError
                InterruptedError
                IsADirectoryError
                NotADirectoryError
                PermissionError
                ProcessLookupError
                TimeoutError
            ReferenceError
            RuntimeError
                NotImplementedError
                RecursionError
            StopAsyncIteration
            StopIteration
            SyntaxError
                IndentationError
                    TabError
            SystemError
            TypeError
            ValueError
                UnicodeError
                    UnicodeDecodeError
                    UnicodeEncodeError
                    UnicodeTranslateError
            Warning
                BytesWarning
                DeprecationWarning
                FutureWarning
                ImportWarning
                PendingDeprecationWarning
                ResourceWarning
                RuntimeWarning
                SyntaxWarning
                UnicodeWarning
                UserWarning
        GeneratorExit
        KeyboardInterrupt
        SystemExit
    

    A commenter asked:

    Say you have a method which pings an external API and you want to handle the exception at a class outside the API wrapper, do you simply return e from the method under the except clause where e is the exception object?

    No, you don't return the exception, just reraise it with a bare raise to preserve the stacktrace.

    try:
        try_this(whatever)
    except SomeException as the_exception:
        handle(the_exception)
        raise
    

    Or, in Python 3, you can raise a new exception and preserve the backtrace with exception chaining:

    try:
        try_this(whatever)
    except SomeException as the_exception:
        handle(the_exception)
        raise DifferentException from the_exception
    

    I elaborate in my answer here.

    0 讨论(0)
  • 2020-11-22 14:03

    Just because no-one else has posted this opinion, I would say

    avoid else clauses in try/excepts because they're unfamiliar to most people

    Unlike the keywords try, except, and finally, the meaning of the else clause isn't self-evident; it's less readable. Because it's not used very often, it'll cause people that read your code to want to double-check the docs to be sure they understand what's going on.

    (I'm writing this answer precisely because I found a try/except/else in my codebase and it caused a wtf moment and forced me to do some googling).

    So, wherever I see code like the OP example:

    try:
        try_this(whatever)
    except SomeException as the_exception:
        handle(the_exception)
    else:
        # do some more processing in non-exception case
        return something
    

    I would prefer to refactor to

    try:
        try_this(whatever)
    except SomeException as the_exception:
        handle(the_exception)
        return  # <1>
    # do some more processing in non-exception case  <2>
    return something
    
    • <1> explicit return, clearly shows that, in the exception case, we are finished working

    • <2> as a nice minor side-effect, the code that used to be in the else block is dedented by one level.

    0 讨论(0)
  • 2020-11-22 14:05

    Python doesn't subscribe to the idea that exceptions should only be used for exceptional cases, in fact the idiom is 'ask for forgiveness, not permission'. This means that using exceptions as a routine part of your flow control is perfectly acceptable, and in fact, encouraged.

    This is generally a good thing, as working this way helps avoid some issues (as an obvious example, race conditions are often avoided), and it tends to make code a little more readable.

    Imagine you have a situation where you take some user input which needs to be processed, but have a default which is already processed. The try: ... except: ... else: ... structure makes for very readable code:

    try:
       raw_value = int(input())
    except ValueError:
       value = some_processed_value
    else: # no error occured
       value = process_value(raw_value)
    

    Compare to how it might work in other languages:

    raw_value = input()
    if valid_number(raw_value):
        value = process_value(int(raw_value))
    else:
        value = some_processed_value
    

    Note the advantages. There is no need to check the value is valid and parse it separately, they are done once. The code also follows a more logical progression, the main code path is first, followed by 'if it doesn't work, do this'.

    The example is naturally a little contrived, but it shows there are cases for this structure.

    0 讨论(0)
  • 2020-11-22 14:06

    Whenever you see this:

    try:
        y = 1 / x
    except ZeroDivisionError:
        pass
    else:
        return y
    

    Or even this:

    try:
        return 1 / x
    except ZeroDivisionError:
        return None
    

    Consider this instead:

    import contextlib
    with contextlib.suppress(ZeroDivisionError):
        return 1 / x
    
    0 讨论(0)
  • 2020-11-22 14:15

    OP, YOU ARE CORRECT. The else after try/except in Python is ugly. it leads to another flow-control object where none is needed:

    try:
        x = blah()
    except:
        print "failed at blah()"
    else:
        print "just succeeded with blah"
    

    A totally clear equivalent is:

    try:
        x = blah()
        print "just succeeded with blah"
    except:
        print "failed at blah()"
    

    This is far clearer than an else clause. The else after try/except is not frequently written, so it takes a moment to figure what the implications are.

    Just because you CAN do a thing, doesn't mean you SHOULD do a thing.

    Lots of features have been added to languages because someone thought it might come in handy. Trouble is, the more features, the less clear and obvious things are because people don't usually use those bells and whistles.

    Just my 5 cents here. I have to come along behind and clean up a lot of code written by 1st-year out of college developers who think they're smart and want to write code in some uber-tight, uber-efficient way when that just makes it a mess to try and read / modify later. I vote for readability every day and twice on Sundays.

    0 讨论(0)
提交回复
热议问题