Comment gérer les exceptions dans une compréhension de liste?

120

J'ai une compréhension de liste en Python dans laquelle chaque itération peut lever une exception.

Par exemple , si j'ai:

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

[1/egg for egg in eggs]

J'obtiendrai une ZeroDivisionErrorexception dans le 3ème élément.

Comment puis-je gérer cette exception et continuer l'exécution de la compréhension de la liste?

La seule façon dont je peux penser est d'utiliser une fonction d'assistance:

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

Mais cela me semble un peu compliqué.

Existe-t-il une meilleure façon de faire cela en Python?

Remarque: Ceci est un exemple simple (voir " par exemple " ci-dessus) que j'ai inventé parce que mon exemple réel nécessite un certain contexte. Je ne suis pas intéressé à éviter la division par zéro erreur, mais à gérer les exceptions dans une compréhension de liste.

Nathan Fellman
la source
4
Il existe un PEP 463 pour ajouter une expression pour gérer les exceptions. Dans votre exemple, ce serait le cas [1/egg except ZeroDivisionError: None for egg in (1,3,0,3,2)]. Mais il est toujours en mode brouillon. Mon instinct est que cela ne sera pas accepté. Les expressions Imho peuvent devenir trop compliquées (vérifier plusieurs exceptions, avoir des combinaisons plus complexes (plusieurs opérateurs logiques, des compréhensions complexes, etc.)
cfi
1
Notez que pour cet exemple spécifique , vous pouvez utiliser un numpy ndarrayavec les paramètres appropriés dans np.seterr. Cela entraînerait 1/0 = nan. Mais je me rends compte que cela ne se généralise pas à d'autres situations où ce besoin se fait sentir.
gerrit

Réponses:

96

Il n'y a pas d'expression intégrée en Python qui vous permet d'ignorer une exception (ou de renvoyer des valeurs alternatives & c en cas d'exceptions), il est donc impossible, littéralement, de "gérer les exceptions dans une compréhension de liste" car une compréhension de liste est une expression contenant une autre expression, rien de plus (c'est-à-dire aucune instruction, et seules les instructions peuvent intercepter / ignorer / gérer les exceptions).

Les appels de fonction sont des expressions et les corps de fonction peuvent inclure toutes les instructions que vous souhaitez, donc déléguer l'évaluation de la sous-expression sujette aux exceptions à une fonction, comme vous l'avez remarqué, est une solution de contournement possible (d'autres, lorsque cela est possible, sont vérifie les valeurs susceptibles de provoquer des exceptions, comme également suggéré dans d'autres réponses).

Les réponses correctes à la question «comment gérer les exceptions dans une compréhension de liste» expriment toutes une partie de toute cette vérité: 1) littéralement, c'est-à-dire lexicalement DANS la compréhension elle-même, vous ne pouvez pas; 2) en pratique, vous déléguez le travail à une fonction ou vérifiez les valeurs sujettes aux erreurs lorsque cela est possible. Votre affirmation répétée selon laquelle ce n’est pas une réponse n’est donc pas fondée.

Alex Martelli
la source
14
Je vois. Donc une réponse complète serait que je devrais soit: 1. utiliser une fonction 2. ne pas utiliser la compréhension de liste 3. essayer d'empêcher l'exception plutôt que de la gérer.
Nathan Fellman
9
Je ne vois pas "ne pas utiliser les compréhensions de liste" comme une partie de la réponse à "comment gérer les exceptions dans les compréhensions de liste", mais je suppose que vous pourriez raisonnablement le voir comme une conséquence possible de " lexicalement DANS le LC, il n'est pas possible de gérer les exceptions », qui est en effet la première partie de la réponse littérale.
Alex Martelli
Pouvez-vous détecter une erreur dans une expression de générateur ou dans la compréhension d'un générateur?
1
@AlexMartelli, une clause d'exception serait-elle si difficile à intégrer dans les futures versions de Python? [x[1] for x in list except IndexError pass]. L'interpréteur n'a-t-il pas pu créer une fonction temporaire pour essayer x[1]?
alancalvitti
@Nathan, 1,2,3 ci-dessus se transforment en énormes maux de tête dans les flux de données fonctionnels où 1. on veut généralement intégrer des fonctions via lambdas; 2. L'alternative consiste à utiliser beaucoup de boucles for imbriquées qui violent le paradigme fonctionnel et conduisent à un code sujet aux erreurs; 3. Souvent, les erreurs sont des ensembles de données complexes ad hoc et latents qui sont, comme le mot latin signifie données, donnés, donc ne peuvent être facilement évités.
alancalvitti
118

Je me rends compte que cette question est assez ancienne, mais vous pouvez également créer une fonction générale pour faciliter ce genre de chose:

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

Ensuite, dans votre compréhension:

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

Vous pouvez bien sûr faire fonctionner la poignée par défaut comme vous le souhaitez (disons que vous préférez retourner «Aucun» par défaut).

J'espère que cela vous aidera, vous ou les futurs téléspectateurs de cette question!

Remarque: dans python 3, je créerais le mot-clé d'argument 'handle' uniquement et le placerais à la fin de la liste d'arguments. Cela rendrait les arguments de passage, etc., beaucoup plus naturels.

Tête de Bryan
la source
2
extrêmement utile, merci. Bien que je sois d'accord avec les commentaires théoriques, cela montre une approche pratique pour résoudre un problème que j'ai eu à plusieurs reprises.
Paul
2
Bonne réponse. Un mod que je suggérerais est également de passer argset kwargsde gérer. De cette façon, vous pouvez retourner say eggau lieu d'un codé en dur 0, ou une exception comme vous le faites.
Mad Physicist
3
Vous pouvez également souhaiter le type d'exception comme argument facultatif (les types d'exception peuvent-ils être paramétrés?), Afin que les exceptions inattendues soient levées vers le haut plutôt que d'ignorer toutes les exceptions.
00prometheus
3
@Bryan, pouvez-vous fournir du code pour "en python 3, je créerais le mot clé d'argument 'handle' uniquement, et je le mettrais à la fin de la liste d'arguments." essayé de placer handleaprès **kwarget a obtenu un SyntaxError. Voulez-vous déréférencer comme kwargs.get('handle',e)?
alancalvitti
21

Vous pouvez utiliser

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

cela sautera simplement les éléments qui sont nuls.

Peter
la source
28
Cela ne répond pas à la question de savoir comment gérer les exceptions dans une compréhension de liste.
Nathan Fellman
8
euh, oui, c'est le cas. cela évite d'avoir à gérer les exceptions. oui, ce n'est pas toujours la bonne solution, mais c'est une solution courante.
Peter
3
Je comprends. Je reprends le commentaire (bien que je ne le supprimerai pas puisque cette courte «discussion» améliore la réponse).
Nathan Fellman
11

Non, il n'y a pas de meilleur moyen. Dans de nombreux cas, vous pouvez utiliser l'évitement comme le fait Peter

Votre autre option est de ne pas utiliser les compréhensions

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

À vous de décider si c'est plus encombrant ou non

John La Rooy
la source
1
Comment utiliserais-je les compréhensions ici?
Nathan Fellman
@Nathan: vous ne le feriez pas. gnibbler dit: Non, il n'y a pas de meilleur moyen
SilentGhost
Désolé ... j'ai raté le 'pas' dans sa réponse :-)
Nathan Fellman
4

Je pense qu'une fonction d'assistance, comme suggéré par celui qui pose la question initiale et Bryan Head aussi, est bonne et pas du tout encombrante. Une seule ligne de code magique qui fait tout le travail n'est tout simplement pas toujours possible, donc une fonction d'assistance est une solution parfaite si l'on veut éviter les forboucles. Cependant, je le modifierais pour celui-ci:

# 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 sortie sera celle-ci [(1/1, None), (1/3, None), (None, ZeroDivisionError), (1/3, None), (1/2, None)]. Avec cette réponse, vous avez le contrôle total pour continuer comme vous le souhaitez.

Elmex80s
la source
Agréable. Cela ressemble beaucoup au Eithertype de certains langages de programmation fonctionnels (comme Scala), où an Eitherpeut contenir une valeur d'un type ou d'un autre, mais pas les deux. La seule différence est que dans ces langues, il est idiomatique de mettre l'erreur sur le côté gauche et la valeur sur le côté droit. Voici un article avec plus d'informations .
Alex Palmer
3

Je n'ai vu aucune réponse mentionner cela. Mais cet exemple serait un moyen d'empêcher la levée d'une exception pour les cas d'échec connus.

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


Output: [1, 0, None, 0, 0]
Slakker
la source
N'est-ce pas la même chose que cette réponse? stackoverflow.com/a/1528244/1084
Nathan Fellman
Il y a une différence subtile. Le filtrage est appliqué sur la sortie plutôt que sur la liste d'entrée. Comme vous pouvez le voir dans l'exemple publié, j'ai noté «Aucun» pour le cas qui pourrait provoquer une exception.
Slakker