Come posso scrivere un'espressione lambda equivalente a:
def x():
raise Exception()
Non è consentito:
y = lambda : raise Exception()
y=lambda...oltre def y:allora?
Come posso scrivere un'espressione lambda equivalente a:
def x():
raise Exception()
Non è consentito:
y = lambda : raise Exception()
y=lambda...oltre def y:allora?
Risposte:
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.
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.
y = 1/0è una soluzione super intelligente se il tipo di eccezione è irrilevante
Che ne dite di:
lambda x: exec('raise(Exception(x))')
SyntaxErrorPython 2.7.11.
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.
Le funzioni create con i moduli lambda non possono contenere istruzioni .
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.
TypeError: <lambda>() takes exactly 1 positional argument (2 given). Sei sicuro di IndexError?
lambda *x: [][0]. (La versione originale accetta solo un argomento; per nessun argomento, usa lambda : [][0]; per due, usa lambda x,y: [][0]; ecc.)
lambda x: {}["I want to show this message. Called with: %s" % x] Produce: KeyError: 'I want to show this message. Called with: foo'
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())
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:
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
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.