El consenso general "¡no use excepciones!" Proviene principalmente de otros idiomas e incluso a veces está desactualizado.
En C ++, lanzar una excepción es muy costoso debido al "desbobinado de la pila". Cada declaración de variable local es como una with
declaración en Python, y el objeto en esa variable puede ejecutar destructores. Estos destructores se ejecutan cuando se produce una excepción, pero también cuando se regresa de una función. Este "modismo RAII" es una característica integral del lenguaje y es muy importante para escribir código robusto y correcto, por lo que RAII versus excepciones baratas fue una compensación que C ++ decidió hacia RAII.
A principios de C ++, una gran cantidad de código no se escribía de manera segura: a menos que realmente use RAII, es fácil perder memoria y otros recursos. Por lo tanto, lanzar excepciones haría que ese código sea incorrecto. Esto ya no es razonable, ya que incluso la biblioteca estándar de C ++ usa excepciones: no puede pretender que no existen excepciones. Sin embargo, las excepciones siguen siendo un problema al combinar código C con C ++.
En Java, cada excepción tiene un seguimiento de pila asociado. El seguimiento de la pila es muy valioso cuando se depuran errores, pero es un esfuerzo inútil cuando la excepción nunca se imprime, por ejemplo, porque solo se usó para el flujo de control.
Entonces, en esos idiomas, las excepciones son "demasiado caras" para ser utilizadas como flujo de control. En Python esto no es un problema y las excepciones son mucho más baratas. Además, el lenguaje Python ya sufre una sobrecarga que hace que el costo de las excepciones sea imperceptible en comparación con otras construcciones de flujo de control: por ejemplo, verificar si existe una entrada dict con una prueba de membresía explícita if key in the_dict: ...
es generalmente tan rápido como simplemente acceder a la entrada the_dict[key]; ...
y verificar si obtener un KeyError. Algunas características integrales del lenguaje (por ejemplo, generadores) están diseñadas en términos de excepciones.
Entonces, si bien no hay una razón técnica para evitar específicamente las excepciones en Python, todavía queda la pregunta de si debe usarlas en lugar de valores de retorno. Los problemas a nivel de diseño con excepciones son:
no son del todo obvios. No puede ver fácilmente una función y ver qué excepciones puede generar, por lo que no siempre sabe qué atrapar. El valor de retorno tiende a estar más bien definido.
Las excepciones son el flujo de control no local que complica su código. Cuando lanza una excepción, no sabe dónde se reanudará el flujo de control. Para los errores que no se pueden manejar de inmediato, esta es probablemente una buena idea, cuando notificar a la persona que llama sobre una condición es completamente innecesario.
La cultura de Python generalmente está inclinada a favor de las excepciones, pero es fácil exagerar. Imagine una list_contains(the_list, item)
función que verifica si la lista contiene un elemento igual a ese elemento. Si el resultado se comunica a través de excepciones que es absolutamente molesto, porque tenemos que llamarlo así:
try:
list_contains(invited_guests, person_at_door)
except Found:
print("Oh, hello {}!".format(person_at_door))
except NotFound:
print("Who are you?")
Devolver un bool sería mucho más claro:
if list_contains(invited_guests, person_at_door):
print("Oh, hello {}!".format(person_at_door))
else:
print("Who are you?")
Si ya se supone que la función devuelve un valor, entonces devolver un valor especial para condiciones especiales es bastante propenso a errores, porque las personas se olvidarán de verificar este valor (esa es probablemente la causa de 1/3 de los problemas en C). Una excepción suele ser más correcta.
Un buen ejemplo es una pos = find_string(haystack, needle)
función que busca la primera aparición de la needle
cadena en la `cadena de pajar y devuelve la posición de inicio. Pero, ¿qué pasa si el cordel de pajar no contiene el cordel de agujas?
La solución de C e imitada por Python es devolver un valor especial. En C esto es un puntero nulo, en Python esto es -1
. Esto conducirá a resultados sorprendentes cuando la posición se use como un índice de cadena sin verificar, especialmente porque -1
es un índice válido en Python. En C, su puntero NULL al menos le dará un segfault.
En PHP, se devuelve un valor especial de un tipo diferente: el booleano en FALSE
lugar de un entero. Resulta que esto no es realmente mejor debido a las reglas de conversión implícitas del lenguaje (¡pero tenga en cuenta que en Python también se pueden usar booleanos como ints!). Las funciones que no devuelven un tipo consistente generalmente se consideran muy confusas.
Una variante más robusta hubiera sido lanzar una excepción cuando no se puede encontrar la cadena, lo que asegura que durante el flujo de control normal es imposible usar accidentalmente el valor especial en lugar de un valor ordinario:
try:
pos = find_string(haystack, needle)
do_something_with(pos)
except NotFound:
...
Alternativamente, siempre se puede devolver un tipo que no se puede usar directamente pero que primero se debe desenvolver, por ejemplo, una tupla de resultado-bool donde el booleano indica si ocurrió una excepción o si el resultado es utilizable. Luego:
pos, ok = find_string(haystack, needle)
if not ok:
...
do_something_with(pos)
Esto lo obliga a manejar los problemas de inmediato, pero se vuelve molesto muy rápidamente. También evita que encadena la función fácilmente. Cada llamada a función ahora necesita tres líneas de código. Golang es un lenguaje que cree que esta molestia vale la pena.
Para resumir, las excepciones no carecen por completo de problemas y definitivamente se pueden usar en exceso, especialmente cuando reemplazan un valor de retorno "normal". Pero cuando se usa para indicar condiciones especiales (no necesariamente solo errores), las excepciones pueden ayudarlo a desarrollar API que sean limpias, intuitivas, fáciles de usar y difíciles de usar mal.
collections.defaultdict
omy_dict.get(key, default)
hace que el código sea mucho más claro quetry: my_dict[key] except: return default
¡NO! - no en general - las excepciones no se consideran buenas prácticas de control de flujo con la excepción de una sola clase de código. El único lugar donde las excepciones se consideran una forma razonable, o incluso mejor, de señalar una condición son las operaciones de generador o iterador. Estas operaciones pueden devolver cualquier valor posible como resultado válido, por lo que se necesita un mecanismo para señalar un final.
Considere leer un archivo binario de transmisión de un byte a la vez; absolutamente cualquier valor es un resultado potencialmente válido, pero aún tenemos que señalar el final del archivo. Por lo tanto, tenemos una opción, devolver dos valores (el valor de byte y un indicador válido) cada vez o generar una excepción cuando no hay más que hacer. En los dos casos, el código consumidor puede verse así:
alternativamente:
Pero esto, desde que PEP 343 fue implementado y portado de nuevo, todo ha sido cuidadosamente envuelto en la
with
declaración. Lo anterior se convierte en el muy pitónico:En python3 esto se convirtió en:
Le recomiendo encarecidamente que lea PEP 343, que ofrece antecedentes, justificación, ejemplos, etc.
También es habitual utilizar una excepción para señalar el final del procesamiento cuando se utilizan funciones de generador para indicar el final.
Me gustaría agregar que su ejemplo de buscador es casi seguro al revés, tales funciones deberían ser generadores, devolviendo la primera coincidencia en la primera llamada, luego llamadas sustituyentes que devuelven la próxima coincidencia y generando una
NotFound
excepción cuando no hay más coincidencias.fuente
if
sería mejor. Cuando se trata de depurar y probar o incluso razonar sobre el comportamiento de sus códigos, es mejor no confiar en las excepciones, deberían ser la excepción. He visto, en el código de producción, un cálculo que regresó a través de un lanzamiento / captura en lugar de un simple retorno, esto significó que cuando el cálculo alcanza un error de división por cero, devuelve un valor aleatorio en lugar de fallar donde ocurrió el error, esto causó varias horas de depuración