213 votes

Définir une expression lambda qui soulève une Exception

Comment puis-je écrire une expression lambda qui est équivalente à :

def x():
    raise Exception()

Ce qui suit n'est pas autorisé :

y = lambda : raise Exception()

6 votes

Donc vous ne pouvez pas faire ça. Utilisez les fonctions normales.

2 votes

Quel est l'intérêt de donner un nom à une fonction anonyme ?

3 votes

@gnibbler Vous pouvez utiliser le nom pour faire référence à la fonction. y() est plus facile à utiliser que (lambda : 0)() dans le REPL.

264voto

Marcelo Cantos Points 91211

Il y a plus d'une façon d'écorcher un python :

y = lambda: (_ for _ in ()).throw(Exception('foobar'))

Les lambdas acceptent les déclarations. Puisque raise ex est une déclaration, vous pourriez rédiger une déclaration d'intérêt général :

def raise_(ex):
    raise ex

y = lambda: raise_(Exception('foobar'))

Mais si votre but est d'éviter un def il est évident que cela ne suffit pas. Elle vous permet cependant de lever des exceptions de manière conditionnelle, par exemple :

y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))

Vous pouvez également lever une exception sans définir une fonction nommée. Tout ce dont vous avez besoin est un estomac solide (et 2.x pour le code donné) :

type(lambda:0)(type((lambda:0).func_code)(
  1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())

Et un python3 estomac solide solution :

type(lambda: 0)(type((lambda: 0).__code__)(
    1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())

Merci @WarrenSpencer d'avoir indiqué une réponse très simple si vous ne vous souciez pas de l'exception levée : y = lambda: 1/0 .

221 votes

OMG quel art sombre c'est ?

28 votes

Si vous ne vous souciez pas du type d'exception déclenchée, la méthode suivante fonctionne également : lambda: 1 / 0 . Vous vous retrouverez avec une ZeroDivisionError lancée au lieu d'une exception normale. Gardez à l'esprit que si l'exception est autorisée à se propager, il peut sembler étrange à quelqu'un déboguant votre code de commencer à voir un tas de ZeroDivisionErrors.

0 votes

Excellente solution @WarrenSpencer. La plupart des codes n'ont pas beaucoup d'erreurs de division par zéro, donc c'est aussi distinctif que si vous pouviez choisir le type vous-même.

94voto

vvkatwss vvkatwss Points 421

Pourquoi pas :

lambda x: exec('raise(Exception(x))')

17 votes

C'est un peu compliqué mais pour écrire des tests où vous voulez simuler des fonctions, cela fonctionne très bien ! !!

21 votes

Ça marche mais tu ne devrais pas le faire.

1 votes

Cela ne fonctionne pas pour moi, j'obtiens une SyntaxError sur Python 2.7.11.

19voto

Michael Scarpa Points 111

En fait, il y a un moyen, mais il est très artificiel.

Vous pouvez créer un objet de code à l'aide de la fonction compile() fonction intégrée. Cela vous permet d'utiliser le raise (ou toute autre instruction, d'ailleurs), mais elle soulève un autre problème : l'exécution de l'objet code. La méthode habituelle consiste à utiliser la fonction exec mais cela nous ramène au problème initial, à savoir qu'il est impossible d'exécuter des instructions dans un fichier de type lambda (ou un eval() d'ailleurs).

La solution est un hack. Les appelables comme le résultat d'un lambda ont tous un attribut __code__ qui peuvent en fait être remplacés. Ainsi, si vous créez un callable et que vous remplacez sa fonction __code__ avec l'objet de code ci-dessus, vous obtenez quelque chose qui peut être évalué sans utiliser d'instructions. Cependant, tout ceci donne lieu à un code très obscur :

map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()

Ce qui précède fait ce qui suit :

  • el compile() crée un objet de code qui soulève l'exception ;

  • el lambda: 0 renvoie un appelable qui ne fait rien d'autre que de retourner la valeur 0 -- ceci est utilisé pour exécuter l'objet de code ci-dessus plus tard ;

  • el lambda x, y, z crée une fonction qui appelle le __setattr__ du premier argument avec les autres arguments, ET RENVOIE LE PREMIER ARGUMENT ! Ceci est nécessaire, car __setattr__ retourne lui-même None ;

  • el map() prend le résultat de lambda: 0 et en utilisant le lambda x, y, z remplace le __code__ avec le résultat de l'opération compile() appel. Le résultat de cette opération de mappage est une liste avec une seule entrée, celle retournée par lambda x, y, z c'est pourquoi nous avons besoin de cette lambda : si nous utilisions __setattr__ tout de suite, nous perdrions la référence à la lambda: 0 Objet !

  • enfin, le premier (et seul) élément de la liste renvoyée par la fonction map() est exécuté, ce qui a pour effet d'appeler l'objet de code, et finalement de lever l'exception souhaitée.

Cela fonctionne (testé dans Python 2.6), mais ce n'est certainement pas très joli.

Une dernière remarque : si vous avez accès à l'application types (ce qui nécessiterait d'utiliser le module import avant votre eval ), alors vous pouvez raccourcir un peu ce code : en utilisant types.FunctionType() vous pouvez créer une fonction qui exécutera l'objet de code donné, de sorte que vous n'aurez pas besoin de créer une fonction fictive avec la commande lambda: 0 et en remplaçant la valeur de son __code__ attribut.

17voto

Kyle Strand Points 2340

Si tout ce que vous voulez est une expression lambda qui soulève une exception arbitraire, vous pouvez accomplir ceci avec une expression illégale. Par exemple, lambda x: [][0] tentera d'accéder au premier élément d'une liste vide, ce qui provoquera une IndexError.

VEUILLEZ NOTER : C'est un hack, pas une fonctionnalité. Ne pas utilisez ceci dans tout code (non codé) qu'un autre être humain pourrait voir ou utiliser.

0 votes

Dans mon cas, j'obtiens : TypeError: <lambda>() takes exactly 1 positional argument (2 given) . Etes-vous sûr de l'IndexError ?

4 votes

Yep. Vous avez peut-être fourni le mauvais nombre d'arguments ? Si vous avez besoin d'une fonction lambda qui peut prendre n'importe quel nombre d'arguments, utilisez lambda *x: [][0] . (La version originale ne prend qu'un seul argument ; pour ne pas avoir d'argument, utilisez lambda : [][0] ; pour deux, utilisez lambda x,y: [][0] ; etc.)

6 votes

Je l'ai un peu élargi : lambda x: {}["I want to show this message. Called with: %s" % x] Produit : KeyError: 'I want to show this message. Called with: foo'

15voto

Mitar Points 1621

Fonctions créées avec des formulaires lambda ne peut contenir de déclarations .

4 votes

C'est vrai, mais cela ne répond pas vraiment à la question ; lever une exception à partir d'une expression lambda est facile à faire (et l'attraper peut parfois être réalisé en utilisant des astuces très contournées, cf. stackoverflow.com/a/50916686/2560053 o stackoverflow.com/a/50961836/2560053 ).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X