¿Cómo manejar excepciones en una lista comprensiva?

120

Tengo una lista de comprensión en Python en la que cada iteración puede generar una excepción.

Por ejemplo , si tengo:

eggs = (1,3,0,3,2)

[1/egg for egg in eggs]

Obtendré una ZeroDivisionErrorexcepción en el tercer elemento.

¿Cómo puedo manejar esta excepción y continuar la ejecución de la comprensión de la lista?

La única forma en que puedo pensar es usar una función auxiliar:

def spam(egg):
    try:
        return 1/egg
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Pero esto me parece un poco engorroso.

¿Existe una mejor manera de hacer esto en Python?

Nota: Este es un ejemplo simple (ver " por ejemplo " arriba) que ideé porque mi ejemplo real requiere algo de contexto. No me interesa evitar la división por cero errores, sino manejar las excepciones en una lista de comprensión.

Nathan Fellman
fuente
4
Hay un PEP 463 para agregar una expresión para manejar excepciones. En tu ejemplo lo sería [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Pero todavía está en modo borrador. Mi instinto es que no será aceptado. En mi opinión, las expresiones pueden volverse demasiado confusas (verificar múltiples excepciones, tener combinaciones más complejas (múltiples operadores lógicos, comprensiones complejas, etc.)
cfi
1
Tenga en cuenta que para este ejemplo específico , podría usar un número ndarraycon la configuración adecuada en np.seterr. Eso daría como resultado 1/0 = nan. Pero me doy cuenta de que eso no se generaliza a otras situaciones en las que surge esta necesidad.
Gerrit

Respuestas:

96

No hay una expresión incorporada en Python que le permita ignorar una excepción (o devolver valores alternativos & c en caso de excepciones), por lo que es imposible, literalmente hablando, "manejar excepciones en una comprensión de lista" porque una comprensión de lista es una expresión que contiene otra expresión, nada más (es decir, no hay declaraciones, y solo las declaraciones pueden detectar / ignorar / manejar excepciones).

Las llamadas a funciones son expresiones, y los cuerpos de las funciones pueden incluir todas las declaraciones que desee, por lo que delegar la evaluación de la subexpresión propensa a excepciones a una función, como ha notado, es una solución alternativa viable (otras, cuando es factible, son verifica los valores que podrían provocar excepciones, como también se sugiere en otras respuestas).

Las respuestas correctas a la pregunta "cómo manejar las excepciones en una comprensión de lista" están expresando parte de toda esta verdad: 1) literalmente, es decir, léxicamente EN la comprensión misma, no se puede; 2) prácticamente, delega el trabajo a una función o verifica los valores propensos a errores cuando es factible. Por tanto, su reiterada afirmación de que esto no es una respuesta carece de fundamento.

Alex Martelli
fuente
14
Veo. Entonces, una respuesta completa sería que yo debería: 1. usar una función 2. no usar la comprensión de listas 3. tratar de prevenir la excepción en lugar de manejarla.
Nathan Fellman
9
No veo "no usar listas por comprensión" como parte de la respuesta a "cómo manejar excepciones en listas por comprensión", pero supongo que podría verlo razonablemente como una posible consecuencia de " léxicamente EN el LC, no es posible manejar excepciones ", que de hecho es la primera parte de la respuesta literal.
Alex Martelli
¿Puede detectar un error en una expresión o comprensión del generador?
1
@AlexMartelli, ¿una cláusula except sería tan difícil de trabajar en futuras versiones de Python? [x[1] for x in list except IndexError pass]. ¿No podría el intérprete crear una función temporal para probar x[1]?
alancalvitti
@Nathan, 1, 2, 3 arriba se convierten en tremendos dolores de cabeza en los flujos de datos funcionales donde 1. uno quiere típicamente funciones en línea a través de lambdas; 2. La alternativa es usar muchos bucles for anidados que violan el paradigma funcional y conducen a un código propenso a errores; 3. a menudo los errores son conjuntos de datos complejos ad-hoc y latentes que, como significa la palabra latina para datos, son dados, por lo que no se pueden prevenir fácilmente.
alancalvitti
118

Me doy cuenta de que esta pregunta es bastante antigua, pero también puede crear una función general para facilitar este tipo de cosas:

def catch(func, handle=lambda e : e, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        return handle(e)

Entonces, en tu comprensión:

eggs = (1,3,0,3,2)
[catch(lambda : 1/egg) for egg in eggs]
[1, 0, ('integer division or modulo by zero'), 0, 0]

Por supuesto, puede hacer que la función de control predeterminada sea lo que desee (digamos que prefiere devolver 'Ninguno' de forma predeterminada).

¡Espero que esto te ayude a ti o a los futuros espectadores de esta pregunta!

Nota: en Python 3, haría solo la palabra clave del argumento 'handle' y la pondría al final de la lista de argumentos. Esto haría que los argumentos de paso y la captura de datos sean mucho más naturales.

Bryan Head
fuente
2
extremadamente útil, gracias. Si bien estoy de acuerdo con los comentarios teóricos, esto muestra un enfoque práctico para resolver un problema que he tenido repetidamente.
Paul
2
Gran respuesta. Un mod que sugeriría es pasar argsy kwargsmanejar también. De esa forma, podría devolver decir en egglugar de un código fijo 0o una excepción como lo está haciendo.
Mad Physicist
3
Es posible que también desee el tipo de excepción como un argumento opcional (¿se pueden parametrizar los tipos de excepción?), De modo que las excepciones inesperadas se muestren hacia arriba en lugar de ignorar todas las excepciones.
00prometheus
3
@Bryan, ¿puede proporcionar código para "en python 3, haría solo la palabra clave del argumento 'handle' y la pondría al final de la lista de argumentos". intenté colocar handledespués **kwargy obtuve un SyntaxError. ¿Quiere desreferenciar como kwargs.get('handle',e)?
Alancalvitti
21

Puedes usar

[1/egg for egg in eggs if egg != 0]

esto simplemente omitirá los elementos que son cero.

Pedro
fuente
28
Esto no responde a la pregunta de cómo manejar las excepciones en una lista de comprensión.
Nathan Fellman
8
umm, sí, lo hace. obvia la necesidad de manejar excepciones. sí, no es la solución correcta todo el tiempo, pero es común.
Peter
3
Entiendo. Retiro el comentario (aunque no lo eliminaré ya que esa breve 'discusión' mejora la respuesta).
Nathan Fellman
11

No, no hay mejor manera. En muchos casos, puedes usar la evasión como lo hace Peter.

Tu otra opción es no usar comprensiones

eggs = (1,3,0,3,2)

result=[]
for egg in eggs:
    try:
        result.append(egg/0)
    except ZeroDivisionError:
        # handle division by zero error
        # leave empty for now
        pass

Depende de usted decidir si eso es más engorroso o no

John La Rooy
fuente
1
¿Cómo usaría las comprensiones aquí?
Nathan Fellman
@Nathan: no lo harías. gnibbler dice: No, no hay mejor manera
SilentGhost
Lo siento ... me perdí el 'no' en su respuesta :-)
Nathan Fellman
4

Creo que una función de ayuda, como sugirió el que hace la pregunta inicial y también Bryan Head, es buena y no es engorrosa en absoluto. Una sola línea de código mágico que hace todo el trabajo no siempre es posible, por lo que una función auxiliar es una solución perfecta si se quiere evitar forbucles. Sin embargo, lo modificaría a este:

# A modified version of the helper function by the Question starter 
def spam(egg):
    try:
        return 1/egg, None
    except ZeroDivisionError as err:
        # handle division by zero error        
        return None, err

La salida será esta [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Con esta respuesta, tiene el control total para continuar de la forma que desee.

Elmex80s
fuente
Agradable. Esto se parece mucho al Eithertipo en algunos lenguajes de programación funcional (como Scala), donde un Eitherpuede contener un valor de un tipo u otro, pero no de ambos. La única diferencia es que en esos idiomas es idiomático poner el error en el lado izquierdo y el valor en el lado derecho. Aquí hay un artículo con más información .
Alex Palmer
3

No vi ninguna respuesta que mencionara esto. Pero este ejemplo sería una forma de evitar que se genere una excepción para casos fallidos conocidos.

eggs = (1,3,0,3,2)
[1/egg if egg > 0 else None for egg in eggs]


Output: [1, 0, None, 0, 0]
Slakker
fuente
¿No es lo mismo que esta respuesta? stackoverflow.com/a/1528244/1084
Nathan Fellman
Hay una sutil diferencia. El filtrado se aplica a la salida en lugar de a la lista de entrada. Como puede ver en el ejemplo publicado, he indicado "Ninguno" para el caso que podría causar una excepción.
Slakker