¿Es una buena práctica usar try-except-else en Python?


De vez en cuando en Python, veo el bloque:

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

¿Cuál es la razón para que el intento-excepto-else exista?

No me gusta ese tipo de programación, ya que está utilizando excepciones para realizar el control de flujo. Sin embargo, si está incluido en el idioma, debe haber una buena razón para ello, ¿no?

Entiendo que las excepciones no son errores, y que solo deben usarse para condiciones excepcionales (por ejemplo, intento escribir un archivo en disco y no hay más espacio, o tal vez no tengo permiso), y no para el control de flujo.

Normalmente manejo las excepciones como:

something = some_default_value
try:
    something = try_this(whatever)
except SomeException as exception:
    #Handle exception
finally:
    return something

O si realmente no quiero devolver nada si ocurre una excepción, entonces:

try:
    something = try_this(whatever)
    return something
except SomeException as exception:
    #Handle exception
Author: Aaron Hall, 2013-04-22

9 answers

"no sé si es por ignorancia, pero no me gusta que tipo de programación, ya que está utilizando excepciones para realizar el control de flujo."

En el mundo de Python, el uso de excepciones para el control de flujo es común y normal.

Incluso los desarrolladores del núcleo de Python usan excepciones para el control de flujo y ese estilo está fuertemente integrado en el lenguaje (es decir, el protocolo iterador usa StopIteration terminación de bucle de señal).

Además, el estilo try-except - se usa para evitar las condiciones de carrera inherentes a algunas de las construcciones "look-before-you-leap". Por ejemplo, pruebas os.camino.existe resulta en información que puede estar desactualizada en el momento en que la usa. Del mismo modo, Cola.completo devuelve información que puede estar obsoleta. El estilo try-except-else producirá código más fiable en estos casos.

" Entiendo que las excepciones no son errores, solo deben utilizarse en condiciones excepcionales "

En algunos otros idiomas, esa regla refleja sus normas culturales como se refleja en sus bibliotecas. La "regla" también se basa en parte en consideraciones de rendimiento para esos idiomas.

La norma cultural Python es algo diferente. En muchos casos, debe usar excepciones para control-flow. Además, el uso de excepciones en Python no ralentiza el código circundante y el código de llamada como lo hace en algunos los lenguajes compilados (es decir, CPython ya implementa código para la comprobación de excepciones en cada paso, independientemente de si realmente utiliza excepciones o no).

En otras palabras, su comprensión de que "las excepciones son para lo excepcional" es una regla que tiene sentido en algunos otros lenguajes, pero no para Python.

" Sin embargo, si está incluido en el lenguaje mismo, debe haber un buena razón para ello, ¿no?"

Además de ayudar a evitar las condiciones de carrera, las excepciones también son muy útiles para tirar de los bucles externos de manejo de errores. Esta es una optimización necesaria en lenguajes interpretados que no tienden a tener un movimiento de código invariante de bucle automático .

Además, las excepciones pueden simplificar bastante el código en situaciones comunes donde la capacidad de manejar un problema está muy lejos de donde surgió el problema. Por ejemplo, es común tener código de interfaz de usuario de nivel superior que llama al código para la lógica de negocios que a su vez llama a rutinas de bajo nivel. Las situaciones que surgen en las rutinas de bajo nivel (como los registros duplicados para claves únicas en los accesos a la base de datos) solo se pueden manejar en código de nivel superior (como pedirle al usuario una nueva clave que no entre en conflicto con las claves existentes). El uso de excepciones para este tipo de control-flujo permite que las rutinas de nivel medio ignoren completamente el problema y estén muy bien desacopladas de ese aspecto del control de flujo.

Hay un buen post en el blog sobre la indispensibilidad de excepciones aquí .

También, vea esta respuesta de desbordamiento de pila: ¿Son excepciones realmente para errores excepcionales?

"¿Cuál es la razón para que el intento-excepto-else exista?"

La cláusula else en sí es interesante. Se ejecuta cuando no hay excepción, pero antes de la cláusula finally. Ese es su propósito principal.

Sin la cláusula else, la única opción para ejecutar código adicional antes de la finalización sería la práctica torpe de agregar el código por la cláusula try. Eso es torpe porque arriesga subir excepciones en código que no estaba destinado a ser protegido por el try-block.

El caso de uso de ejecutar código no protegido adicional antes de la finalización no se presenta muy a menudo. Por lo tanto, no esperes ver muchos ejemplos en el código publicado. Es algo raro.

Otro caso de uso para la cláusula else es realizar acciones que deben ocurrir cuando no ocurre ninguna excepción y que no ocurren cuando se manejan excepciones. Para ejemplo:

   recip = float('Inf')
   try:
       recip = 1 / f(x)
   except ZeroDivisionError:
       logging.info('Infinite result')
   else:
       logging.info('Finite result')

Por último, el uso más común de una cláusula else en un bloque de prueba es para un poco de embellecimiento (alinear los resultados excepcionales y los no excepcionales al mismo nivel de sangría). Este uso es siempre opcional y no es estrictamente necesario.

 449
Author: Raymond Hettinger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-31 01:10:48

¿Cuál es la razón para que exista el intento-excepto-otra cosa?

Un bloque try le permite manejar un error esperado. El bloque except solo debería capturar excepciones que esté preparado para manejar. Si maneja un error inesperado, su código puede hacer lo incorrecto y ocultar errores.

Una cláusula else se ejecutará si no hubo errores, y al no ejecutar ese código en el bloque try, evitará detectar un error inesperado. Una vez más, la captura de un error inesperado puede ocultar bichos.

Ejemplo

Por ejemplo:

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

La suite "try, except" tiene dos cláusulas opcionales, else y finally. Así que en realidad es try-except-else-finally.

else evaluará solo si no hay excepción del bloque try. Nos permite simplificar el código más complicado a continuación:

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

Así que si comparamos un else con la alternativa (que podría crear errores) vemos que reduce las líneas de código y podemos tener una más legible, mantenible, y menos código base con errores.

finally

finally se ejecutará sin importar qué, incluso si otra línea está siendo evaluada con una sentencia return.

Desglosado con pseudo-código

Podría ayudar a desglosar esto, en la forma más pequeña posible que muestre todas las características, con comentarios. Supongamos que este pseudocódigo sintácticamente correcto (pero no ejecutable a menos que se definan los nombres) está en una función.

Por ejemplo:

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

Es verdad que podríamos incluir el código en el bloque else en el bloque try en su lugar, donde se ejecutaría si no hubiera excepciones, pero ¿qué pasaría si ese código en sí mismo genera una excepción del tipo que estamos capturando? Dejarlo en el bloque try ocultaría ese error.

Queremos minimizar las líneas de código en el bloque try para evitar la captura de excepciones que no esperábamos, bajo el principio de que si nuestro código falla, queremos que falle en voz alta. Este es un mejor practice .

Entiendo que las excepciones no son errores{[30]]}

En Python, la mayoría de las excepciones son errores.

Podemos ver la jerarquía de excepciones usando pydoc. Por ejemplo, en Python 2:

$ python -m pydoc exceptions

O Python 3:

$ python -m pydoc builtins

Nos dará la jerarquía. Podemos ver que la mayoría de los tipos de Exception son errores, aunque Python usa algunos de ellos para cosas como terminar bucles for (StopIteration). Esto es Python 3 jerarquía:

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

Un comentarista preguntó:

Supongamos que tiene un método que hace ping a una API externa y desea manejar la excepción en una clase fuera del envoltorio de la API, ¿simplemente devuelve e desde el método bajo la cláusula except donde e es el objeto exception?

No, no se devuelve la excepción, solo se vuelve a subir con un desnudo raise para preservar la traza apilada.

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

O, en Python 3, puede generar una nueva excepción y preservar backtrace with exception chaining:

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

Explico enmi respuesta aquí .

 112
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-31 01:14:06

Python no suscribe la idea de que las excepciones solo deben usarse para casos excepcionales, de hecho el modismo es 'pide perdón, no permiso'. Esto significa que el uso de excepciones como parte rutinaria de su control de flujo es perfectamente aceptable y, de hecho, se recomienda.

Esto es generalmente algo bueno, ya que trabajar de esta manera ayuda a evitar algunos problemas (como un ejemplo obvio, las condiciones de carrera a menudo se evitan), y tiende a hacer que el código sea un poco más legible.

Imagine que tiene una situación en la que toma una entrada de usuario que necesita ser procesada, pero tiene un valor predeterminado que ya está procesada. La estructura try: ... except: ... else: ... hace que el código sea muy legible:

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

Compare con cómo podría funcionar en otros idiomas:

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

Tenga en cuenta las ventajas. No hay necesidad de comprobar que el valor es válido y analizarlo por separado, se hacen una vez. El código también sigue una progresión más lógica, la ruta del código principal es la primera, seguida de 'si no funciona, haz esto.

El ejemplo es naturalmente un poco artificial, pero muestra que hay casos para esta estructura.

 28
Author: Gareth Latty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-22 01:59:14

¿Es una buena práctica usar try-except-else en python?

La respuesta a esto es que depende del contexto. Si haces esto:

d = dict()
try:
    item = d['item']
except KeyError:
    item = 'default'

Demuestra que no conoces muy bien Python. Esta funcionalidad está encapsulada en el método dict.get:

item = d.get('item', 'default')

El try/except block es una forma mucho más visual y detallada de escribir lo que se puede ejecutar eficientemente en una sola línea con un método atómico. Hay otros casos en los que esto es verdad.

Sin embargo, eso no significa que debamos evitar todo manejo de excepciones. En algunos casos se prefiere evitar las condiciones de carrera. No compruebe si existe un archivo, simplemente intente abrirlo y capture el IOError apropiado. En aras de la simplicidad y la legibilidad, trate de encapsular esto o factorizarlo como apropiado.

Lee el Zen de Python, entendiendo que hay principios que están en tensión, y ten cuidado con el dogma que se basa demasiado en cualquiera de las declaraciones.

 14
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-03 16:54:25

Debes tener cuidado al usar el bloque finally, ya que no es lo mismo que usar un bloque else en el intento, excepto. El bloque final se ejecutará independientemente del resultado de la excepción de prueba.

In [10]: dict_ = {"a": 1}

In [11]: try:
   ....:     dict_["b"]
   ....: except KeyError:
   ....:     pass
   ....: finally:
   ....:     print "something"
   ....:     
something

Como todo el mundo ha señalado, el uso del bloque else hace que su código sea más legible, y solo se ejecuta cuando no se lanza una excepción

In [14]: try:
             dict_["b"]
         except KeyError:
             pass
         else:
             print "something"
   ....:
 4
Author: Greg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-24 00:53:17

Cada vez que vea esto:

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

O incluso esto:

try:
    return 1 / x
except ZeroDivisionError:
    return None

Considere esto en su lugar:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    return 1 / x
 4
Author: Rajiv Bakulesh Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-13 23:40:49

Este es mi fragmento simple sobre cómo entender try-except-else-finally block en 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))

Probemos div 1/1:

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

Probemos div 1/0

div(1, 0)
Zero Division Error detected
Finally the division of 1/0 is done
 1
Author: zakiakhmad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-06 06:24:45

Vea el siguiente ejemplo que ilustra todo sobre try-except-else-finally:

for i in range(3):
    try:
        y = 1 / i
    except ZeroDivisionError:
        print(f"\ti = {i}")
        print("\tError report: ZeroDivisionError")
    else:
        print(f"\ti = {i}")
        print(f"\tNo error report and y equals {y}")
    finally:
        print("Try block is run.")

Implementarlo y venir por:

    i = 0
    Error report: ZeroDivisionError
Try block is run.
    i = 1
    No error report and y equals 1.0
Try block is run.
    i = 2
    No error report and y equals 0.5
Try block is run.
 0
Author: JawSaw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-25 05:04:02

OP, TIENES RAZÓN. El else después de try/except en Python es feo. conduce a otro objeto de control de flujo donde no se necesita ninguno:

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

Un equivalente totalmente claro es:

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

Esto es mucho más claro que una cláusula else. El otro después de try / except no se escribe con frecuencia, por lo que toma un momento calcular cuáles son las implicaciones.

Solo porque puedas hacer algo, no significa que debas hacer algo.

Muchas características han sido añadido a los idiomas porque alguien pensó que podría ser útil. El problema es que, cuantas más características, menos claras y obvias son las cosas porque la gente no suele usar esas campanas y silbatos.

Solo mis 5 centavos aquí. Tengo que venir detrás y limpiar una gran cantidad de código escrito por desarrolladores de 1er año fuera de la universidad que piensan que son inteligentes y quieren escribir código de alguna manera uber-apretado, uber-eficiente cuando eso solo hace que sea un desastre para tratar de leer / modificar más tarde. Yo voto por legibilidad todos los días y dos veces los domingos.

 -3
Author: Kevin J. Rice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-20 16:03:11