¿Es mejor ‘probar’ algo y capturar la excepción o la prueba si es posible primero evitar una excepción?

¿Debo probar if algo es válido o simplemente try hacerlo y detectar la excepción?

  • ¿Hay alguna documentación sólida que diga que se prefiere una forma?
  • ¿Es una forma más pythonica ?

Por ejemplo, debería:

 if len(my_list) >= 4: x = my_list[3] else: x = 'NO_ABC' 

O:

 try: x = my_list[3] except IndexError: x = 'NO_ABC' 

Algunos pensamientos…
PEP 20 dice:

Los errores nunca deben pasar en silencio.
A menos que sea explícitamente silenciado.

¿Debería usar un try lugar de un if debe interpretarse como un error que pasa en silencio? Y si es así, ¿lo estás silenciando explícitamente usándolo de esta manera, por lo tanto, haciéndolo bien?


No me refiero a situaciones en las que solo puedes hacer las cosas de una manera; por ejemplo:

 try: import foo except ImportError: import baz 

Debería preferir try/except sobre if/else si eso resulta en

  • aceleraciones (por ejemplo, evitando búsquedas adicionales)
  • Código más limpio (menos líneas / más fácil de leer)

A menudo, estos van de la mano.


aceleraciones

En el caso de tratar de encontrar un elemento en una lista larga por:

 try: x = my_list[index] except IndexError: x = 'NO_ABC' 

el bash, excepto que es la mejor opción cuando el index está probablemente en la lista y el IndexError generalmente no se levanta. De esta manera, evitará la necesidad de una búsqueda adicional if index < len(mylist) .

Python fomenta el uso de excepciones, que usted maneja es una frase de Dive Into Python . Su ejemplo no solo maneja la excepción (con gracia), en lugar de dejarla pasar en silencio , también la excepción ocurre solo en el caso excepcional de que no se encuentre el índice (¡de ahí la palabra excepción !).


código limpiador

La documentación oficial de Python menciona EAFP : es más fácil pedir perdón que el permiso y Rob Knight señala que detectar errores en lugar de evitarlos puede resultar en un código más limpio y fácil de leer. Su ejemplo lo dice así:

Peor (LBYL 'mira antes de saltar') :

 #check whether int conversion will raise an error if not isinstance(s, str) or not s.isdigit: return None elif len(s) > 10: #too many digits for int conversion return None else: return int(str) 

Mejor (EAFP: más fácil pedir perdón que permiso) :

 try: return int(str) except (TypeError, ValueError, OverflowError): #int conversion failed return None 

En este caso particular, deberías usar algo completamente distinto:

 x = myDict.get("ABC", "NO_ABC") 

Sin embargo, en general: si espera que la prueba falle con frecuencia, use if . Si la prueba es costosa en relación con solo intentar la operación y detectar la excepción si falla, use try . Si ninguna de estas condiciones se aplica, vaya con lo que sea más fácil de leer.

Si es trivial comprobar si algo va a fallar antes de hacerlo, probablemente debería favorecerlo. Después de todo, la construcción de excepciones (incluyendo sus seguimientos asociados) lleva tiempo.

Las excepciones deben usarse para:

  1. cosas que son inesperadas, o …
  2. cosas en las que necesita saltar más de un nivel de lógica (por ejemplo, donde una break no le permite llegar lo suficientemente lejos), o …
  3. cosas en las que no sabe exactamente qué va a manejar la excepción antes de tiempo, o …
  4. las cosas en las que es costoso verificar el fracaso con anticipación (en relación con solo intentar la operación)

Tenga en cuenta que, a menudo, la respuesta real es “ninguna”, por ejemplo, en su primer ejemplo, lo que realmente debería hacer es usar .get() para proporcionar un valor predeterminado:

 x = myDict.get('ABC', 'NO_ABC') 

Usar try y except directamente en lugar de dentro de un guardia si siempre se debe hacer si existe alguna posibilidad de una condición de carrera. Por ejemplo, si desea asegurarse de que existe un directorio, no haga esto:

 import os, sys if not os.path.isdir('foo'): try: os.mkdir('foo') except OSError, e print e sys.exit(1) 

Si otro hilo o proceso crea el directorio entre isdir y mkdir , saldrá. En su lugar, haga esto:

 import os, sys, errno try: os.mkdir('foo') except OSError, e if e.errno != errno.EEXIST: print e sys.exit(1) 

Eso solo se cerrará si no se puede crear el directorio ‘foo’.

¿Debería usar un bash en lugar de un si debe interpretarse como un error que pasa en silencio? Y si es así, ¿lo estás silenciando explícitamente usándolo de esta manera, por lo tanto, haciéndolo bien?

El uso de try es reconocer que un error puede pasar, que es lo opuesto a pasarlo en silencio. Usar except está causando que no pase en absoluto.

El uso de try: except: se prefiere en los casos donde if: else: logic es más complicado. Lo simple es mejor que lo complejo; Complejo es mejor que complicado; y es más fácil pedir perdón que permiso.

Lo que advierte sobre los “errores que nunca deben pasar en silencio” es el caso en el que el código podría generar una excepción que usted conoce, y donde su diseño admite la posibilidad, pero no ha diseñado de manera tal que se pueda tratar con la excepción. En mi opinión, silenciar explícitamente un error sería hacer algo como pass en un bloque de except , lo que solo debe hacerse con el entendimiento de que “no hacer nada” en realidad es el manejo correcto de errores en la situación particular. (Esta es una de las pocas veces en las que siento que un comentario en un código bien escrito probablemente sea realmente necesario).

Sin embargo, en su ejemplo particular, ninguno es apropiado:

 x = myDict.get('ABC', 'NO_ABC') 

La razón por la que todos lo señalan, aunque reconoces tu deseo de comprender en general y la incapacidad de dar un mejor ejemplo, es que existen pasos laterales equivalentes en muchos casos, y buscarlos es la clave. Primer paso para resolver el problema.

Como mencionan los otros posts, depende de la situación. Existen algunos peligros con el uso de try / except en lugar de verificar la validez de sus datos por adelantado, especialmente cuando se usan en proyectos más grandes.

  • El código en el bloque try puede tener la oportunidad de causar todo tipo de esgulps antes de que se detecte la excepción. Si se comprueba de forma proactiva de antemano con una instrucción if, puede evitar esto.
  • Si el código al que se llama en su bloque try genera un tipo de excepción común, como TypeError o ValueError, es posible que no detecte la misma excepción que esperaba capturar, puede ser otra cosa que genere la misma clase de excepción antes o después de llegar la línea donde se puede levantar su excepción.

por ejemplo, supongamos que tienes:

 try: x = my_list[index_list[3]] except IndexError: x = 'NO_ABC' 

IndexError no dice nada sobre si ocurrió al intentar obtener un elemento de index_list o my_list.

Para un significado general, puede considerar leer Modismos y Antiidómicos en Python: Excepciones .

En su caso particular, como han dicho otros, debe usar dict.get() :

obtener (clave [, por defecto])

Devuelve el valor para clave si la clave está en el diccionario, de lo contrario, el valor predeterminado Si no se proporciona el valor predeterminado, el valor predeterminado es Ninguno, por lo que este método nunca genera un KeyError.

Siempre que use try/except flujo de control, pregúntese:

  1. ¿Es fácil ver cuándo el bloque try tiene éxito y cuándo falla?
  2. ¿Eres consciente de todos los efectos secundarios dentro del bloque try ?
  3. ¿Conoce todos los casos en los que el bloque try lanza la excepción?
  4. Si la implementación del bloque try cambia, ¿se seguirá comportando su flujo de control como se esperaba?

Si la respuesta a una o más de estas preguntas es “no”, puede haber mucho perdón que pedir; más probable de su futuro yo.


Un ejemplo. Recientemente vi el código en un proyecto más grande que se veía así:

 try: y = foo(x) except ProgrammingError: y = bar(x) 

Hablando con el progtwigdor, resultó que el flujo de control previsto era:

Si x es un entero, haz y = foo (x)

y si x es una lista de enteros, haz y = barra (x).

Esto funcionó porque foo realizó una consulta de base de datos y la consulta sería exitosa si x era un entero y lanzaba un ProgrammingError si x era una lista.

Usar try/except es una mala elección aquí:

  1. El nombre de la excepción, ProgrammingError , no revela el problema real (que x no es un número entero). Eso hace que sea difícil averiguar qué está pasando.
  2. ProgrammingError se genera durante una llamada a la base de datos, lo que innecesariamente desperdicia tiempo. Las cosas se volverían realmente horribles si resultara que foo escribe algo en la base de datos antes de lanzar una excepción, o altera el estado de algún otro sistema.
  3. No está claro si cada ProgrammingError se debe a que x es una lista. Supongamos, por ejemplo, que hay un error tipográfico en la consulta de la base de datos de foo . Esto también podría provocar un ProgrammingError . La consecuencia es que ahora también se llama a la bar(x) si x es un entero. Esto podría generar excepciones crípticas o producir resultados imprevisibles.
  4. El bloque try/except agrega un requisito a todas las implementaciones futuras de foo . Cada vez que cambiemos foo , ahora debemos pensar en cómo maneja las listas y asegurarnos de que arroje un error de ProgrammingError y no, digamos, un error de AttributeError o ningún error.