Excepciones de Python: EAFP y ¿Qué es realmente excepcional?

Se ha dicho en un par de lugares ( aquí y aquí ) que el énfasis de Python en “es más fácil pedir perdón que permiso” (EAFP) debe atenuarse con la idea de que las excepciones solo deben llamarse en casos realmente excepcionales. Considere lo siguiente, en el que estamos haciendo estallar y empujando en una cola de prioridad hasta que solo quede un elemento:

import heapq ... pq = a_list[:] heapq.heapify(pq) while True: min1 = heapq.heappop(pq) try: min2 = heapq.heappop(pq) except IndexError: break else heapq.heappush(pq, min1 + min2) # do something with min1 

La excepción solo se produce una vez en las iteraciones len(a_list) del bucle, pero no es realmente excepcional, porque sabemos que va a suceder con el tiempo. Esta configuración nos a_list comprobar si a_list está vacía a_list veces, pero (quizás) es menos legible que usar condiciones explícitas.

¿Cuál es el consenso sobre el uso de excepciones para este tipo de lógica de progtwig no excepcional?

Las excepciones solo deben ser llamadas en casos verdaderamente excepcionales.

No en Python: por ejemplo, cada bucle for (a menos que se break prematuramente o return s) termina por una excepción ( StopIteration ) que se lanza y se captura. Por lo tanto, una excepción que ocurre una vez por bucle no es extraña para Python, ¡está ahí más a menudo que no!

El principio en cuestión puede ser crucial en otros idiomas, pero eso definitivamente no es razón para aplicar ese principio a Python, donde es tan contrario al espíritu del idioma.

En este caso, me gusta la reescritura de Jon (que debería simplificarse aún más eliminando la twig else) porque hace que el código sea más compacto: una razón pragmática, definitivamente no es el “temperamento” del estilo Python con un principio extraño.

Lanzar excepciones es costoso en la mayoría de los lenguajes de bajo nivel como C ++. Eso influye en gran parte de la “sabiduría común” sobre las excepciones, y no se aplica tanto a los idiomas que se ejecutan en una máquina virtual, como Python. Python no tiene un costo tan alto por usar una excepción en lugar de un condicional.

(Este es un caso en el que la “sabiduría común” se convierte en una cuestión de hábito. La gente adquiere experiencia en un tipo de entorno, los idiomas de bajo nivel, y luego lo aplica a nuevos dominios sin evaluar si tiene sentido. )

Las excepciones siguen siendo, en general, excepcionales. Eso no significa que no ocurran a menudo; significa que son la excepción Son las cosas que tienden a romperse con el flujo de código ordinario, y que la mayoría de las veces no quiere tener que manejar una por una, que es el punto de los controladores de excepciones. Esta parte es la misma en Python que en C ++ y todos los demás lenguajes con excepciones.

Sin embargo, eso tiende a definir cuándo se lanzan las excepciones. Estás hablando de cuándo deberían ser atrapadas las excepciones. Muy simple, no te preocupes por eso: las excepciones no son caras, así que no te esfuerces en intentar evitar que se lancen. Una gran cantidad de código de Python está diseñado alrededor de esto.

No estoy de acuerdo con la sugerencia de Jon de intentar probar y evitar excepciones por adelantado. Eso está bien si conduce a un código más claro, como en su ejemplo. Sin embargo, en muchos casos solo va a complicar las cosas, ya que puede llevar a la duplicación de cheques e introducir errores. Por ejemplo,

 import os, errno, stat def read_file(fn): """ Read a file and return its contents. If the file doesn't exist or can't be read, return "". """ try: return open(fn).read() except IOError, e: return "" def read_file_2(fn): """ Read a file and return its contents. If the file doesn't exist or can't be read, return "". """ if not os.access(fn, os.R_OK): return "" st = os.stat(fn) if stat.S_ISDIR(st.st_mode): return "" return open(fn).read() print read_file("x") 

Claro, podemos probar y evitar el fallo, pero hemos complicado mucho las cosas. Estamos tratando de adivinar todas las formas en que el acceso al archivo puede fallar (y esto no los detecta a todos), podemos haber introducido condiciones de carrera y estamos haciendo mucho más trabajo de E / S. Todo esto está hecho para nosotros, solo atrapa la excepción.

En cuanto a los documentos , creo que puede volver a escribir la función de manera segura de la siguiente manera:

 import heapq ... pq = heapq.heapify(a_list) while pq: min1 = heapq.heappop(pq) if pq: min2 = heapq.heappop(pq) heapq.heappush(pq, min1 + min2) # do something with min1 

..y por lo tanto evitar el try-except.

Llegar al final de una lista, que es algo que sabes que va a pasar aquí, no es excepcional, ¡se ofrece! Así que una mejor práctica sería manejarlo por adelantado. Si tuviera algo más en otro subproceso que consumiera del mismo montón, entonces usar try-excepto que tendría mucho más sentido (es decir, manejar un caso especial / impredecible).

De manera más general, evitaría las excepciones de prueba siempre que pueda probar y evitar un fallo por adelantado. Esto te obliga a decir: “Sé que esta mala situación podría suceder, así que así es como lo trato”. En mi opinión, tenderás a escribir un código más legible como resultado.

[Editar] Actualizado el ejemplo según la sugerencia de Alex

Solo para que conste, yo escribiría es así:

 import heapq a_list = range(20) pq = a_list[:] heapq.heapify(pq) try: while True: min1 = heapq.heappop(pq) min2 = heapq.heappop(pq) heapq.heappush(pq, min1 + min2) except IndexError: pass # we ran out of numbers in pq 

Las excepciones pueden dejar un bucle (incluso funciones) y puede usarlas para eso. Como Python los lanza a todas partes, creo que este patrón es bastante útil (incluso pythonic ).

Descubrí que la práctica de usar excepciones como herramientas de control de flujo “normales” es bastante aceptada en Python. Se usa más comúnmente en situaciones como la que usted describe, cuando llega al final de algún tipo de secuencia.

En mi opinión, ese es un uso perfectamente válido de una excepción. Sin embargo, debes tener cuidado con el uso del manejo de excepciones. Generar una excepción es una operación razonablemente costosa y, por lo tanto, es mejor asegurarse de que solo confíe en una excepción al final de la secuencia, no en cada iteración.