Definire un'espressione lambda che genera un'eccezione


137

Come posso scrivere un'espressione lambda equivalente a:

def x():
    raise Exception()

Non è consentito:

y = lambda : raise Exception()

2
Quindi non puoi farlo. Usa le normali funzioni.
DrTyrsa,

1
Qual è lo scopo di dare un nome a una funzione anonima?
John La Rooy,

2
@gnibbler È possibile utilizzare il nome per fare riferimento alla funzione. y () è più facile da usare di (lambda: 0) () nella REPL.
Thomas Jung,

Allora, qual è il vantaggio di y=lambda...oltre def y:allora?
John La Rooy,

@gnibbler Qualche contesto: volevo definire una funzione def g (f, e) che chiama f nel caso felice ed e se è stato rilevato un errore. A seconda dello scenario e potrebbe sollevare un'eccezione o restituire un valore valido. Per usare g volevo scrivere g (lambda x: x * 2, lambda e: raise e) o in alternativa g (lambda x: x * 2, lambda e: 0).
Thomas Jung,

Risposte:


169

C'è più di un modo per scuoiare un Python:

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

Lambdas accetta dichiarazioni. Poiché raise exè una dichiarazione, è possibile scrivere un raiser per scopi generici:

def raise_(ex):
    raise ex

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

Ma se il tuo obiettivo è quello di evitare un def, questo ovviamente non lo taglia. Tuttavia, consente di sollevare condizionalmente eccezioni, ad esempio:

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

In alternativa è possibile sollevare un'eccezione senza definire una funzione con nome. Tutto ciò che serve è uno stomaco forte (e 2.x per il codice dato):

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

E una soluzione di stomaco forte python3 :

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

Grazie @WarrenSpencer per aver ricordato una risposta molto semplice, se non si cura che viene sollevata un'eccezione: y = lambda: 1/0.


117
OMG che arte oscura è?
CodeColorist,

11
Se non si cura ciò che è gettato tipo di eccezione, le seguenti opere anche: lambda: 1 / 0. Ti ritroverai a lanciare un ZeroDivisionError invece di una normale eccezione. Tieni presente che se l'eccezione può essere propagata, potrebbe sembrare strano che qualcuno esegua il debug del codice per iniziare a vedere un gruppo di ZeroDivisionErrors.
Warren Spencer,

Ottima soluzione @ WarrenSpencer. La maggior parte del codice non ha molti errori di divisione zero, quindi è distintiva come se potessi scegliere tu stesso il tipo.
jwg,

2
y = 1/0è una soluzione super intelligente se il tipo di eccezione è irrilevante
Saher Ahwal,

3
Qualcuno può parlarci di ciò che sta realmente accadendo nelle soluzioni "arte oscura / stomaco forte"?
decimale

56

Che ne dite di:

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

12
È abbastanza confuso ma per scrivere test in cui si desidera deridere funzioni funziona perfettamente !!!
Kannan Ekanath,

8
Funziona ma non dovresti farlo.
Augurar

1
Questo non funziona per me, ho un SyntaxErrorPython 2.7.11.
Nick Sweeting,

Ricevo anche l'errore sopra (SyntaxError) su Python 2.7.5
Dinesh

1
questo è specifico di Python 3, tuttavia non credo che Python 2 lo consenta.
Saher Ahwal,

16

In realtà, c'è un modo, ma è molto inventato.

È possibile creare un oggetto codice utilizzando la compile()funzione integrata. Ciò consente di utilizzare l' raiseistruzione (o qualsiasi altra istruzione, per quella materia), ma solleva un'altra sfida: l'esecuzione dell'oggetto codice. Il solito modo sarebbe usare l' execistruzione, ma questo ti riporta al problema originale, vale a dire che non puoi eseguire istruzioni in lambda(o an eval(), del resto).

La soluzione è un hack. I callable come il risultato di lambdaun'istruzione hanno tutti un attributo __code__, che può essere effettivamente sostituito. Quindi, se si crea un callable e si sostituisce il suo __code__valore con l'oggetto codice dall'alto, si ottiene qualcosa che può essere valutato senza utilizzare le istruzioni. Raggiungere tutto ciò, tuttavia, si traduce in un codice molto oscuro:

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

Quanto sopra fa quanto segue:

  • la compile()chiamata crea un oggetto codice che genera l'eccezione;

  • i lambda: 0rendimenti di un richiamabili che non fa altro che restituire il valore 0 - questo è usato per eseguire il codice oggetto sopra indicato entro;

  • la lambda x, y, zcrea una funzione che chiama il __setattr__metodo del primo argomento con i restanti argomenti, e restituisce il PRIMO ARGOMENTO! Questo è necessario, perché esso __setattr__stesso ritorna None;

  • la map()chiamata prende il risultato di lambda: 0, e usando il lambda x, y, zrimpiazza è l' __code__oggetto con il risultato della compile()chiamata. Il risultato di questa operazione sulla mappa è un elenco con una voce, quella restituita da lambda x, y, z, motivo per cui ne abbiamo bisogno lambda: se usassimo __setattr__subito, perderemmo il riferimento lambda: 0all'oggetto!

  • infine, map()viene eseguito il primo (e unico) elemento dell'elenco restituito dalla chiamata, con il risultato che l'oggetto del codice viene chiamato, sollevando infine l'eccezione desiderata.

Funziona (testato in Python 2.6), ma sicuramente non è carino.

Un'ultima nota: se hai accesso al typesmodulo (che richiederebbe di usare l' importistruzione prima del tuo eval), allora puoi abbreviare un po 'questo codice: usando types.FunctionType()puoi creare una funzione che eseguirà l'oggetto del codice dato, quindi hai vinto Non è necessario l'hacking per creare una funzione fittizia lambda: 0e sostituire il valore del suo __code__attributo.



15

Se tutto ciò che vuoi è un'espressione lambda che solleva un'eccezione arbitraria, puoi farlo con un'espressione illegale. Ad esempio, lambda x: [][0]tenterà di accedere al primo elemento in un elenco vuoto, che genererà un IndexError.

NOTA : questo è un trucco, non una funzione. Non usarlo in nessun codice (non di golf di codice) che un altro essere umano potrebbe vedere o usare.


Nel mio caso ottengo: TypeError: <lambda>() takes exactly 1 positional argument (2 given). Sei sicuro di IndexError?
Jovik

4
Sì. Hai forse fornito il numero sbagliato di argomenti? Se hai bisogno di una funzione lambda che può accettare qualsiasi numero di argomenti, usa lambda *x: [][0]. (La versione originale accetta solo un argomento; per nessun argomento, usa lambda : [][0]; per due, usa lambda x,y: [][0]; ecc.)
Kyle Strand

3
Ho ampliato un po 'questo: lambda x: {}["I want to show this message. Called with: %s" % x] Produce: KeyError: 'I want to show this message. Called with: foo'
ErlVolton

@ErlVolton Clever! Anche se usarlo ovunque tranne che in uno script una tantum sembra un'idea terribile ...
Kyle Strand

Sto usando temporaneamente i test unitari per un progetto in cui non mi sono preso la briga di prendere in giro il mio logger. Si alza se si tenta di registrare un errore o critico. Quindi ... Sì, terribile, sebbene consensuale :)
ErlVolton,

10

Vorrei dare una spiegazione dell'aggiornamento 3 della risposta fornita da Marcelo Cantos:

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

Spiegazione

lambda: 0è un'istanza della builtins.functionclasse.
type(lambda: 0)è la builtins.functionclasse.
(lambda: 0).__code__è un codeoggetto.
Un codeoggetto è un oggetto che contiene il codice byte compilato tra le altre cose. È definito qui in CPython https://github.com/python/cpython/blob/master/Include/code.h . I suoi metodi sono implementati qui https://github.com/python/cpython/blob/master/Objects/codeobject.c . Possiamo eseguire la guida sull'oggetto codice:

Help on code object:

class code(object)
 |  code(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring,
 |        constants, names, varnames, filename, name, firstlineno,
 |        lnotab[, freevars[, cellvars]])
 |  
 |  Create a code object.  Not for the faint of heart.

type((lambda: 0).__code__)è la classe di codice.
Quindi quando diciamo

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

stiamo chiamando il costruttore dell'oggetto codice con i seguenti argomenti:

  • argcount = 1
  • kwonlyargcount = 0
  • nlocals = 1
  • stacksize = 1
  • flags = 67
  • codestring = b '| \ 0 \ 202 \ 1 \ 0'
  • costanti = ()
  • nomi = ()
  • varnames = ( 'x',)
  • filename = ''
  • name = ''
  • firstlineno = 1
  • lnotab = b ''

Puoi leggere cosa significano gli argomenti nella definizione di PyCodeObject https://github.com/python/cpython/blob/master/Include/code.h . Il valore di 67 per l' flagsargomento è ad esempio CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE.

L'argomento più importante è quello codestringche contiene i codici operativi dell'istruzione. Vediamo cosa significano.

>>> import dis
>>> dis.dis(b'|\0\202\1\0')
          0 LOAD_FAST                0 (0)
          2 RAISE_VARARGS            1
          4 <0>

La documentazione degli opcode è disponibile qui https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions . Il primo byte è il codice operativo per LOAD_FAST, il secondo byte è il suo argomento, ovvero 0.

LOAD_FAST(var_num)
    Pushes a reference to the local co_varnames[var_num] onto the stack.

Quindi inseriamo il riferimento xnello stack. Il varnamesè una lista di stringhe contenente solo 'x'. Inseriremo l'unico argomento della funzione che stiamo definendo nello stack.

Il byte successivo è il codice operativo per RAISE_VARARGSe il byte successivo è il suo argomento, ovvero 1.

RAISE_VARARGS(argc)
    Raises an exception using one of the 3 forms of the raise statement, depending on the value of argc:
        0: raise (re-raise previous exception)
        1: raise TOS (raise exception instance or type at TOS)
        2: raise TOS1 from TOS (raise exception instance or type at TOS1 with __cause__ set to TOS)

TOS è il top di gamma. Poiché abbiamo inserito il primo argomento ( x) della nostra funzione nello stack ed argcè 1, aumenteremo xse si tratta di un'istanza di eccezione o ne creiamo un'istanza xe la solleviamo altrimenti.

L'ultimo byte, ovvero 0, non viene utilizzato. Non è un codice operativo valido. Potrebbe anche non esserci.

Tornando allo snippet di codice stiamo comunque cercando:

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

Abbiamo chiamato il costruttore dell'oggetto codice:

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

Passiamo l'oggetto codice e un dizionario vuoto al costruttore di un oggetto funzione:

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

Chiamiamo aiuto su un oggetto funzione per vedere cosa significano gli argomenti.

Help on class function in module builtins:

class function(object)
 |  function(code, globals, name=None, argdefs=None, closure=None)
 |  
 |  Create a function object.
 |  
 |  code
 |    a code object
 |  globals
 |    the globals dictionary
 |  name
 |    a string that overrides the name from the code object
 |  argdefs
 |    a tuple that specifies the default argument values
 |  closure
 |    a tuple that supplies the bindings for free variables

Chiamiamo quindi la funzione costruita passando un'istanza di eccezione come argomento. Di conseguenza abbiamo chiamato una funzione lambda che solleva un'eccezione. Eseguiamo lo snippet e vediamo che funziona davvero come previsto.

>>> type(lambda: 0)(type((lambda: 0).__code__)(
...     1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
... )(Exception())
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
  File "", line 1, in 
Exception

miglioramenti

Abbiamo visto che l'ultimo byte del bytecode è inutile. Non ingombriamo agevolmente questa complicata espressione. Rimuoviamo quel byte. Inoltre, se vogliamo giocare un po 'a golf, potremmo omettere l'istanza di Exception e passare invece la classe Exception come argomento. Tali modifiche comporterebbero il seguente codice:

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

Quando lo eseguiremo otterremo lo stesso risultato di prima. È solo più breve.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.