Как я могу написать лямбда-выражение, которое эквивалентно:
def x():
raise Exception()
Следующее не допускается:
y = lambda : raise Exception()
y=lambda...сравнению с этим def y:?
Как я могу написать лямбда-выражение, которое эквивалентно:
def x():
raise Exception()
Следующее не допускается:
y = lambda : raise Exception()
y=lambda...сравнению с этим def y:?
Ответы:
Существует несколько способов создания оболочки для Python:
y = lambda: (_ for _ in ()).throw(Exception('foobar'))
Лямбды принимают заявления. Поскольку raise exэто утверждение, вы можете написать универсальный рейзер:
def raise_(ex):
raise ex
y = lambda: raise_(Exception('foobar'))
Но если ваша цель состоит в том, чтобы избежать def, это, очевидно, не сокращает это. Это, однако, позволяет условно вызывать исключения, например:
y = lambda x: 2*x if x < 10 else raise_(Exception('foobar'))
В качестве альтернативы вы можете вызвать исключение без определения именованной функции. Все, что вам нужно, это сильный желудок (и 2.x для данного кода):
type(lambda:0)(type((lambda:0).func_code)(
1,1,1,67,'|\0\0\202\1\0',(),(),('x',),'','',1,''),{}
)(Exception())
И сильный желудочный раствор python3 :
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
Спасибо @WarrenSpencer за указание очень простой ответ , если вы не заботитесь , которая возбуждается исключение: y = lambda: 1/0.
lambda: 1 / 0. Вы просто получите ZeroDivisionError вместо обычного исключения. Имейте в виду, что если исключение разрешено распространять, то для отладчика вашего кода может показаться странным, что он начинает видеть кучу ошибок ZeroDivisionErrors.
y = 1/0это супер-умное решение, если тип исключения не имеет значения
Как насчет:
lambda x: exec('raise(Exception(x))')
SyntaxErrorна Python 2.7.11.
На самом деле, есть способ, но он очень надуманный.
Вы можете создать объект кода с помощью compile()встроенной функции. Это позволяет вам использовать raiseоператор (или любой другой оператор, в этом отношении), но это поднимает другую проблему: выполнение объекта кода. Обычный способ - использовать execоператор, но это возвращает вас к исходной проблеме, а именно к тому, что вы не можете выполнять операторы в lambda(или в eval()этом отношении).
Решение взломать. Вызываемые элементы, такие как результат lambdaоператора, имеют атрибут __code__, который может быть заменен. Таким образом, если вы создадите вызываемый объект и замените его __code__значение на объект кода из приведенного выше, вы получите что-то, что может быть оценено без использования операторов. Однако достижение всего этого приводит к очень непонятному коду:
map(lambda x, y, z: x.__setattr__(y, z) or x, [lambda: 0], ["__code__"], [compile("raise Exception", "", "single"])[0]()
Выше делает следующее:
compile()вызов создает объект кода , который вызывает исключение;
то lambda: 0возвращается отзывной , который не делает ничего , но возвращает значение 0 - используется для выполнения выше код объекта позже;
the lambda x, y, zсоздает функцию, которая вызывает __setattr__метод первого аргумента с оставшимися аргументами и возвращает первый аргумент! Это необходимо, потому что __setattr__само возвращается None;
map()вызов принимает результат lambda: 0, и использование lambda x, y, zего заменяет в __code__объект с результатом compile()вызова. Результатом этой операции сопоставления является список с одной записью, возвращаемой lambda x, y, z, поэтому нам нужно это lambda: если бы мы использовали __setattr__сразу, мы потеряли бы ссылку на lambda: 0объект!
наконец, выполняется первый (и единственный) элемент списка, возвращаемый map()вызовом, в результате чего вызывается объект кода, что в конечном итоге вызывает желаемое исключение.
Это работает (протестировано в Python 2.6), но это определенно не красиво.
Последнее замечание: если у вас есть доступ к typesмодулю (который потребует использования importоператора перед вашим eval), то вы можете немного сократить этот код: используя types.FunctionType()вы можете создать функцию, которая будет выполнять данный объект кода, так что вы выиграли не нужно создавать фиктивную функцию lambda: 0и заменять значение ее __code__атрибута.
Функции, созданные с помощью лямбда-форм, не могут содержать операторов .
Если все, что вам нужно, это лямбда-выражение, которое вызывает произвольное исключение, вы можете сделать это с помощью недопустимого выражения. Например, lambda x: [][0]попытается получить доступ к первому элементу в пустом списке, что вызовет IndexError.
ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ : это взломать, а не функция. Не используйте это в любом коде (не в коде), который другой человек может увидеть или использовать.
TypeError: <lambda>() takes exactly 1 positional argument (2 given). Вы уверены в IndexError?
lambda *x: [][0]. (Оригинальная версия принимает только один аргумент; без аргументов - используйте lambda : [][0]; для двух - используйте lambda x,y: [][0]; и т. Д.)
lambda x: {}["I want to show this message. Called with: %s" % x] Производит: KeyError: 'I want to show this message. Called with: foo'
Я хотел бы дать ОБНОВЛЕНИЕ 3 ответа, предоставленного Марсело Кантос:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
lambda: 0это экземпляр builtins.functionкласса.
type(lambda: 0)это builtins.functionкласс.
(lambda: 0).__code__это codeобъект. Объект является объектом , который содержит скомпилированный байткод среди других вещей. Это определено здесь в CPython https://github.com/python/cpython/blob/master/Include/code.h . Его методы реализованы здесь https://github.com/python/cpython/blob/master/Objects/codeobject.c . Мы можем запустить справку по объекту кода: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__)это класс кода
Поэтому, когда мы говорим,
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
мы вызываем конструктор объекта кода со следующими аргументами:
Вы можете прочитать о том, что значат аргументы, в определении PyCodeObject
https://github.com/python/cpython/blob/master/Include/code.h . flagsНапример, значение 67 для аргумента CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE.
Наиболее важным аргументом является аргумент, codestringкоторый содержит коды операций. Посмотрим, что они значат.
>>> import dis
>>> dis.dis(b'|\0\202\1\0')
0 LOAD_FAST 0 (0)
2 RAISE_VARARGS 1
4 <0>
Документацию по кодам операций можно найти здесь
https://docs.python.org/3.8/library/dis.html#python-bytecode-instructions . Первый байт - это код операции LOAD_FAST, второй байт - его аргумент, т. Е. 0.
LOAD_FAST(var_num)
Pushes a reference to the local co_varnames[var_num] onto the stack.
Поэтому мы помещаем ссылку xв стек. Это varnamesсписок строк, содержащих только 'x'. Мы поместим единственный аргумент функции, которую мы определяем, в стек.
Следующий байт - это код операции, RAISE_VARARGSа следующий байт - его аргумент, т. Е. 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 является вершиной стека. Так как мы поместили первый аргумент ( x) нашей функции в стек и равны argc1, мы будем увеличивать
x если это экземпляр исключения, или делать его, xи поднимать его в противном случае.
Последний байт, т. Е. 0, не используется. Это не действительный код операции. С таким же успехом может и не быть.
Возвращаясь к фрагменту кода, мы в любом случае:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)(Exception())
Мы вызвали конструктор объекта кода:
type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b'')
Мы передаем объект кода и пустой словарь в конструктор объекта функции:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1\0',(),(),('x',),'','',1,b''),{}
)
Давайте вызовем справку для объекта функции, чтобы увидеть, что означают аргументы.
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
Затем мы вызываем созданную функцию, передавая экземпляр Exception в качестве аргумента. Следовательно, мы вызвали лямбда-функцию, которая вызывает исключение. Давайте запустим фрагмент и посмотрим, что он действительно работает как задумано.
>>> 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
Мы видели, что последний байт байт-кода бесполезен. Давайте не будем загромождать это сложное выражение иголками. Давайте удалим этот байт. Также, если мы хотим немного поиграть в гольф, мы можем опустить создание Exception и вместо этого передать класс Exception в качестве аргумента. Эти изменения приведут к следующему коду:
type(lambda: 0)(type((lambda: 0).__code__)(
1,0,1,1,67,b'|\0\202\1',(),(),('x',),'','',1,b''),{}
)(Exception)
Когда мы запустим его, мы получим тот же результат, что и раньше. Это просто короче.