Jak napisać wyrażenie lambda, które jest równoważne z:
def x():
raise Exception()
Niedozwolone jest:
y = lambda : raise Exception()
y=lambda...nad def y:tym?
Jak napisać wyrażenie lambda, które jest równoważne z:
def x():
raise Exception()
Niedozwolone jest:
y = lambda : raise Exception()
y=lambda...nad def y:tym?
Odpowiedzi:
Istnieje więcej niż jeden sposób na skórowanie Pythona:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Lambdy przyjmują oświadczenia. Ponieważ raise exjest to stwierdzenie, możesz napisać podniesienie ogólnego przeznaczenia:
def raise_(ex):
raise ex
y = lambda: raise_(Exception('foobar'))
Ale jeśli Twoim celem jest uniknięcie a def, to oczywiście nie wystarczy. Umożliwia jednak warunkowe zgłaszanie wyjątków, np .:
y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))
Alternatywnie możesz zgłosić wyjątek bez definiowania nazwanej funkcji. Wystarczy mocny żołądek (i 2.x dla podanego kodu):
type(lambda:0)(type((lambda:0).func_code)(
1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())
I mocne rozwiązanie żołądkowe Python3 :
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
Dzięki @WarrenSpencer za wskazanie bardzo prostą odpowiedź, jeśli nie obchodzi który jest wyjątek: y = lambda: 1/0.
lambda: 1 / 0. Po prostu skończysz z wyrzuceniem ZeroDivisionError zamiast zwykłego wyjątku. Należy pamiętać, że jeśli wyjątek może się rozprzestrzeniać, ktoś debugujący kod może wyglądać dziwnie, aby zobaczyć kilka błędów ZeroDivisionErrors.
y = 1/0jest super inteligentnym rozwiązaniem, jeśli typ wyjątku jest nieistotny
Co powiesz na:
lambda x: exec('raise(Exception(x))')
SyntaxErrorna Python 2.7.11.
Właściwie jest sposób, ale jest bardzo wymyślony.
Możesz utworzyć obiekt kodu za pomocą funkcji compile()wbudowanej. Pozwala to na użycie raiseinstrukcji (lub dowolnej innej instrukcji), ale rodzi inne wyzwanie: wykonanie obiektu kodu. Zwykłym sposobem byłoby użycie execinstrukcji, ale to prowadzi cię z powrotem do pierwotnego problemu, a mianowicie, że nie możesz wykonać instrukcji w a lambda(lub eval(), jeśli o to chodzi).
Rozwiązaniem jest włamanie. Wszystkie wywołania, takie jak wynik lambdainstrukcji, mają atrybut __code__, który w rzeczywistości można zastąpić. Tak więc, jeśli utworzysz wywoływalny i zastąpisz jego __code__wartość obiektem kodu z góry, otrzymasz coś, co można ocenić bez użycia instrukcji. Jednak osiągnięcie tego wszystkiego skutkuje bardzo niejasnym kodem:
map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()
Powyższe wykonuje następujące czynności:
compile()wezwanie tworzy obiekt kodu, który podnosi wyjątek;
gdy lambda: 0wraca wywoływalnym że nic nie robi, ale zwraca wartość 0 - to jest wykorzystywane do wykonywania powyższego celu kodu później;
lambda x, y, ztworzy funkcję, która wywołuje __setattr__metodę pierwszego argumentu z pozostałych argumentów i zwraca pierwszy argument! Jest to konieczne, ponieważ __setattr__samo wraca None;
map()rozmowa odbywa wynik lambda: 0, a przy użyciu lambda x, y, zzastępuje go za __code__obiekt o wyniku compile()rozmowy. Rezultatem tej operacji na mapie jest lista z jednym wpisem, tym zwróconym przez lambda x, y, z, dlatego potrzebujemy tego lambda: gdybyśmy użyli __setattr__od razu, stracilibyśmy odniesienie do lambda: 0obiektu!
na koniec wykonywany jest pierwszy (i jedyny) element listy zwracany przez map()wywołanie, co powoduje wywołanie obiektu kodu, ostatecznie wywołując żądany wyjątek.
Działa (przetestowany w Pythonie 2.6), ale zdecydowanie nie jest ładny.
Ostatnia uwaga: jeśli masz dostęp do typesmodułu (co wymagałoby użycia importinstrukcji przed Twoim eval), to możesz nieco skrócić ten kod: używając types.FunctionType()możesz stworzyć funkcję, która wykona dany obiekt kodu, więc wygrałeś Nie potrzebuję hackowania tworzenia funkcji fikcyjnej zi lambda: 0zastępowania wartości jej __code__atrybutu.
Funkcje utworzone za pomocą formularzy lambda nie mogą zawierać instrukcji .
Jeśli wszystko, czego chcesz, to wyrażenie lambda, które wywołuje dowolny wyjątek, możesz to zrobić za pomocą niedozwolonego wyrażenia. Na przykład lambda x: [][0]spróbuje uzyskać dostęp do pierwszego elementu z pustej listy, co spowoduje zgłoszenie błędu IndexError.
UWAGA : To hack, a nie funkcja. Nie używaj tego w żadnym (innym niż code-golf) kodzie, który może zobaczyć lub użyć inny człowiek.
TypeError: <lambda>() takes exactly 1 positional argument (2 given). Czy na pewno wystąpił błąd IndexError?
lambda *x: [][0]. (Oryginalna wersja przyjmuje tylko jeden argument; bez argumentów użyj lambda : [][0]; dla dwóch użyj lambda x,y: [][0]; itp.)
lambda x: {}["I want to show this message. Called with: %s" % x] Produkuje: KeyError: 'I want to show this message. Called with: foo'
Chciałbym wyjaśnić AKTUALIZACJĘ 3 odpowiedzi udzielonej przez Marcelo Cantos:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
lambda: 0jest instancją builtins.functionklasy.
type(lambda: 0)jest builtins.functionklasą.
(lambda: 0).__code__jest codeprzedmiotem. Obiekt jest obiektem, który posiada skompilowany kod bajtowy między innymi. Jest zdefiniowany tutaj w CPython https://github.com/python/cpython/blob/master/Include/code.h . Jego metody są zaimplementowane tutaj https://github.com/python/cpython/blob/master/Objects/codeobject.c . Możemy uruchomić pomoc dotyczącą obiektu kodu:code
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__)to klasa kodu.
Więc kiedy mówimy
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
wywołujemy konstruktora obiektu kodu z następującymi argumentami:
O tym, co oznaczają argumenty, możesz przeczytać w definicji PyCodeObject
https://github.com/python/cpython/blob/master/Include/code.h . Na przykład wartość 67 flagsargumentu to CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE.
Najważniejszym argumentem jest ten, codestringktóry zawiera kody instrukcji. Zobaczmy, co mają na myśli.
>>> import dis
>>> dis.dis(b'|\0\202\1\0')
0 LOAD_FAST 0 (0)
2 RAISE_VARARGS 1
4 <0>
Dokumentację kodów operacyjnych można znaleźć tutaj
https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions . Pierwszy bajt to kod LOAD_FASToperacji, drugi bajt to jego argument, tj. 0.
LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
Więc umieszczamy odniesienie do xna stosie. varnamesJest listą ciągów zawierających tylko „x”. Jedyny argument funkcji, którą definiujemy, wrzucimy na stos.
Następny bajt to kod operacji dla, RAISE_VARARGSa następny bajt to jego argument, tj. 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 jest szczytem stosu. Ponieważ umieściliśmy pierwszy argument ( x) naszej funkcji na stosie i argcwynosi 1, podniesiemy wartość,
xjeśli jest to wyjątek, lub utworzymy instancję xi podniesiemy ją w przeciwnym razie.
Ostatni bajt, czyli 0, nie jest używany. To nie jest prawidłowy kod operacji. Równie dobrze może go tam nie być.
Wracając do fragmentu kodu, który teraz analizujemy:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
Nazwaliśmy konstruktora obiektu kodu:
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
Przekazujemy obiekt kodu i pusty słownik do konstruktora obiektu funkcji:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)
Wywołajmy pomoc dotyczącą obiektu funkcji, aby zobaczyć, co oznaczają argumenty.
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
Następnie wywołujemy skonstruowaną funkcję, przekazując jako argument wystąpienie wyjątku. W konsekwencji nazwaliśmy funkcję lambda, która zgłasza wyjątek. Uruchommy fragment i przekonajmy się, że rzeczywiście działa zgodnie z przeznaczeniem.
>>> 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
Widzieliśmy, że ostatni bajt kodu bajtowego jest bezużyteczny. Nie zaśmiecajmy tej skomplikowanej ekspresji igliwie. Usuńmy ten bajt. Również jeśli chcemy trochę pograć w golfa, możemy pominąć tworzenie wystąpienia Exception i zamiast tego przekazać klasę Exception jako argument. Te zmiany spowodowałyby następujący kod:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)
Kiedy go uruchomimy, uzyskamy taki sam wynik jak poprzednio. Jest po prostu krótszy.