¿Es mejor 'intentar' algo y detectar la excepción o probar si es posible primero evitar una excepción?

139

¿Debo probar que ifalgo es válido o simplemente tryhacerlo y detectar la excepción?

  • ¿Existe alguna documentación sólida que diga que se prefiere una forma?
  • ¿Es una forma más pitónica ?

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'

Algunas reflexiones ...
PEP 20 dice:

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

¿Debería usarse un en trylugar de un ifcomo un error que pasa silenciosamente? Y si es así, ¿lo está silenciando explícitamente al usarlo de esta manera, por lo tanto, está bien?


Estoy no se refiere a situaciones en las que sólo se puede hacer las cosas 1 manera; por ejemplo:

try:
    import foo
except ImportError:
    import baz
chown
fuente

Respuestas:

161

Usted debe preferir try/exceptsobre if/elsesi ese resultado 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 intentar encontrar un elemento en una larga lista:

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

el intento, excepto es la mejor opción cuando indexprobablemente está en la lista y el IndexError generalmente no se genera. De esta forma, evita la necesidad de una búsqueda adicional if index < len(my_list).

Python fomenta el uso de excepciones, que manejas es una frase de Dive Into Python . Su ejemplo no solo maneja la excepción (con gracia), en lugar de dejarla pasar silenciosamente , 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 más limpio

La documentación oficial de Python menciona EAFP : es más fácil pedir perdón que permiso y Rob Knight señala que detectar errores en lugar de evitarlos puede generar 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(s)

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

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None
Remi
fuente
if index in mylistprueba si el índice es un elemento de mylist, no un índice posible. Te gustaría en su if index < len(mylist)lugar.
ychaouche
1
Esto tiene sentido, pero ¿dónde encuentro la documentación que deja en claro qué posibles excepciones se pueden lanzar para int (). docs.python.org/3/library/functions.html#int No puedo encontrar esta información aquí.
BrutalSimplicity
Al contrario, puede agregar este caso (de off. Doc ) a su respuesta, cuando prefiera if/elsequetry/catch
rappongy
20

En este caso particular, debe usar algo completamente distinto:

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

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

duskwuff -inactive-
fuente
1
+1 para la explicación bajo el ejemplo de código, que es perfecto.
ktdrv
44
Creo que está bastante claro que esto no es lo que estaba preguntando, y ahora ha editado la publicación para que sea aún más claro.
agf
9

El uso tryy exceptdirectamente en lugar de dentro de un ifguardia siempre debe hacerse 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 isdiry mkdir, saldrá. En cambio, haz esto:

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

Eso solo saldrá si no se puede crear el directorio 'foo'.

John Cowan
fuente
7

Si es trivial verificar si algo fallará antes de hacerlo, probablemente debería favorecerlo. Después de todo, la construcción de excepciones (incluidas las trazas asociadas) lleva tiempo.

Se deben usar excepciones para:

  1. cosas que son inesperadas, o ...
  2. cosas donde necesita saltar más de un nivel de lógica (por ejemplo, donde a breakno lo lleva lo suficientemente lejos), o ...
  3. cosas donde no sabes exactamente qué va a manejar la excepción con anticipación, o ...
  4. cosas en las que la verificación anticipada de fallas es costosa (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 debe hacer es usar .get()para proporcionar un valor predeterminado:

x = myDict.get('ABC', 'NO_ABC')
Ámbar
fuente
excepto que, en if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'realidad, suele ser más rápido que usar get, desafortunadamente. No decir que este es el criterio más importante, pero es algo a tener en cuenta.
agf
@agf: Mejor escribir código claro y conciso. Si algo necesita ser optimizado más tarde, es fácil regresar y reescribirlo, pero c2.com/cgi/wiki?PrematureOptimization
Ámbar
Yo que; Mi punto era eso if / elsey try / exceptpuede tener su lugar incluso cuando hay alternativas específicas para cada caso porque tienen diferentes características de rendimiento.
agf
@agf, ¿sabe si el método get () se mejorará en futuras versiones para ser (al menos) tan rápido como buscar explícitamente? Por cierto, al buscar dos veces (como en 'ABC' en d: d ['ABC']), intente: d ['ABC'] excepto KeyError: ... ¿no es el más rápido?
Remi
2
@Remi La parte lenta de .get()es la búsqueda de atributos y la sobrecarga de llamadas a funciones en el nivel de Python; el uso de palabras clave en los elementos integrados básicamente va directamente a C. No creo que vaya a ser mucho más rápido en el corto plazo. En cuanto a ifvs. try, el método read dict.get () devuelve un puntero que tiene información de rendimiento. La proporción de aciertos y errores es importante ( trypuede ser más rápida si la clave casi siempre existe) al igual que el tamaño del diccionario.
agf
5

Como mencionan las otras publicaciones, 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 usa en proyectos más grandes.

  • El código en el bloque de prueba puede tener la posibilidad de causar todo tipo de estragos antes de que se detecte la excepción; si verifica de forma proactiva de antemano con una declaración if, puede evitar esto.
  • Si el código invocado 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 detectar; puede ser algo más que genere la misma clase de excepción antes o después de llegar a la línea donde se puede plantear su excepción.

por ejemplo, suponga que tiene:

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

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

Peter
fuente
4

¿Debería usar un try en lugar de un si se interpreta como un error que pasa silenciosamente? Y si es así, ¿lo está silenciando explícitamente al usarlo de esta manera, por lo tanto, está bien?

Usar tryes reconocer que puede pasar un error, que es lo opuesto a que pase en silencio. El uso exceptestá causando que no pase en absoluto.

Usando try: except:se prefiere en los casos en que if: else:la lógica es más complicado. Simple es mejor que complejo; Complejo es mejor que complicado; y es más fácil pedir perdón que permiso.

Lo que advierte "los errores 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 una manera para lidiar con la excepción. En mi opinión, silenciar explícitamente un error sería hacer algo como passen un exceptbloque, que solo debe hacerse con el entendimiento de que "no hacer nada" es realmente el manejo correcto de errores en una situación particular. (Esta es una de las pocas veces en que siento que un comentario en un código bien escrito probablemente sea realmente necesario).

Sin embargo, en su ejemplo particular, ninguno de los dos es apropiado:

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

La razón por la que todos están señalando esto, a pesar de que reconoces tu deseo de entender en general y tu incapacidad para encontrar un mejor ejemplo, es que los pasos secundarios equivalentes en realidad existen en muchos casos, y buscarlos es el primer paso para resolver el problema.

Karl Knechtel
fuente
3

Siempre que lo use try/exceptpara controlar el flujo, pregúntese:

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

Si la respuesta a una o más de estas preguntas es 'no', podría haber mucho perdón para pedir; muy probablemente de tu futuro yo.


Un ejemplo. Recientemente vi 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 programador, resultó que el flujo de control previsto era:

Si x es un número entero, haz y = foo (x).

Si x es una lista de enteros, haga y = bar (x).

Esto funcionó porque foorealizó una consulta a la base de datos y la consulta sería exitosa si xfuera un entero y arrojaría un ProgrammingErrorif si xfuera una lista.

Usar try/exceptes una mala elección aquí:

  1. El nombre de la excepción, ProgrammingErrorno revela el problema real (que xno es un número entero), lo que dificulta ver lo que está sucediendo.
  2. El ProgrammingErrorse genera durante una llamada a la base de datos, lo que desperdicia tiempo. Las cosas se pondrían realmente horribles si resulta que fooescribe algo en la base de datos antes de que arroje una excepción o altere el estado de algún otro sistema.
  3. No está claro si ProgrammingErrorsolo se genera cuando xhay una lista de enteros. Supongamos, por ejemplo, que hay un error tipográfico en foola consulta de la base de datos. Esto también podría elevar a ProgrammingError. La consecuencia es que bar(x)ahora también se llama cuando xes un número entero. Esto podría generar excepciones crípticas o producir resultados imprevisibles.
  4. El try/exceptbloque 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 ProgrammingErrory no, digamos, un AttributeErrorerror o ningún error.
Elias Strehle
fuente
0

Para un significado general, puede considerar leer modismos y anti-modismos en Python: excepciones .

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

get (clave [, predeterminado])

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

etuardu
fuente
No creo que el enlace cubra esta situación en absoluto. Sus ejemplos son sobre el manejo de cosas que representan errores reales , no sobre si usar el manejo de excepciones para situaciones esperadas.
agf
El primer enlace está desactualizado.
Timofei Bondarev