Визначте лямбда-вираз, що викликає виняток


137

Як я можу написати лямбда-вираз, еквівалентний:

def x():
    raise Exception()

Не дозволяється:

y = lambda : raise Exception()

2
Тож ви не можете цього зробити. Використовуйте звичайні функції.
DrTyrsa

1
Який сенс давати ім’я анонімній функції?
Джон Ла Руй

2
@gnibbler Ім'я можна використовувати для позначення функції. y () простіше у використанні, ніж (лямбда: 0) () в REPL.
Томас Юнг

Так що ж таке перевага y=lambda...над def y:тоді?
Джон Ла Руй

@gnibbler Деякий контекст: я хотів визначити функцію def g (f, e), яка викликає f у щасливому випадку, і e, якщо виявлена ​​помилка. Залежно від сценарію e, можна створити виняток або повернути деяке дійсне значення. Для використання g я хотів написати g (lambda x: x * 2, lambda e: підняти e) або альтернативно g (lambda x: x * 2, lambda e: 0).
Томас Юнг

Відповіді:


169

Існує більше ніж один спосіб виправити 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.


117
OMG, що це за темне мистецтво?
CodeColorist

11
Якщо ви не хвилює , що тип виключення кинуто також наступні роботи: lambda: 1 / 0. Ви просто закінчите, що ZeroDivisionError буде викинутий замість звичайного винятку. Майте на увазі, що якщо виняток дозволяється пропонувати, може здатися дивним, що хтось налагоджує ваш код, щоб почати бачити купу ZeroDivisionErrors.
Воррен Спенсер

Чудове рішення @WarrenSpencer. Більшість кодів не має великої кількості нульових помилок поділу, тому він такий же відмінний, як якщо б ви могли самі вибрати тип.
jwg

2
y = 1/0це супер розумне рішення, якщо тип
винятку

3
Чи може хтось говорити з нами через те, що насправді відбувається в рішеннях "темно-мистецтво / сильний шлунок"?
декларації

56

Як щодо:

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

12
Це досить хакі, але для написання тестів, де ви хочете знущатися над функціями, це працює акуратно !!!
Каннан Еканат

8
Працює, але не варто цього робити.
серпень

1
Це не працює для мене, я отримую SyntaxErrorна Python 2.7.11.
Nick Sweeting

Я також отримую вищезгадану помилку (SyntaxError) на Python 2.7.5
Дінеш,

1
це характерно для python 3, проте я не думаю, що python 2 дозволяє це.
Сахер Аваль,

16

Власне, спосіб є, але це дуже надумано.

Ви можете створити об'єкт коду за допомогою 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 - використовується для виконання вище код об'єкта пізніше;

  • 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__атрибута.


15

Функції, створені за допомогою лямбда-форм, не можуть містити заяви .


2
Це правда, але насправді це не відповідає на питання; Зробити виняток з лямбдаського виразу легко (і знайти його іноді можна досягти, використовуючи дуже надумані трюки, див. stackoverflow.com/a/50916686/2560053 або stackoverflow.com/a/50961836/2560053 ).
Томас Баручель

15

Якщо все, що вам потрібно, - лямбда-вираз, який породжує довільне виняток, ви можете виконати це незаконним виразом. Наприклад, lambda x: [][0]спробує отримати доступ до першого елемента в порожньому списку, який підніме IndexError.

УВАГА ! Примітка . Це хак, а не функція. Не використовуйте це в будь-якому (без коду гольфі) коді, який інша людина може бачити чи використовувати.


У моєму випадку я отримую: TypeError: <lambda>() takes exactly 1 positional argument (2 given). Ви впевнені в IndexError?
Джовік

4
Так. Ви, можливо, навели неправильну кількість аргументів? Якщо вам потрібна функція лямбда, яка може приймати будь-яку кількість аргументів, використовуйте lambda *x: [][0]. (Первісна версія бере лише один аргумент; для жодних аргументів не використовуйте lambda : [][0]; для двох, використовуйте lambda x,y: [][0]тощо)
Kyle Strand

3
Я трохи розширив це lambda x: {}["I want to show this message. Called with: %s" % x] : KeyError: 'I want to show this message. Called with: foo'
Produces

@ErlVolton Розумний! Хоча використання цього в будь-якому місці, крім одноразового сценарію, здається жахливою ідеєю ...
Кайл Странд

Я тимчасово використовую в одиницях тести для проекту, де я не намагався зробити справжній глуз з мого лісоруба. Він піднімається, якщо ви спробуєте зареєструвати помилку чи критично. Отож ... Так страшно, хоча і консенсусно :)
ЕрлВолтон

10

Я хотів би дати пояснення ОНОВЛЕННЯ 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'')

ми викликаємо конструктор об'єкта коду з такими аргументами:

  • аргумент = 1
  • kwonlyargcount = 0
  • nлокалі = 1
  • stacksize = 1
  • прапори = 67
  • codestring = b '| \ 0 \ 202 \ 1 \ 0'
  • константи = ()
  • імена = ()
  • varnames = ('x',)
  • filename = ''
  • name = ''
  • firstlineno = 1
  • lnotab = b ''

Ви можете прочитати про те, що означають аргументи у визначенні PyCodeObject https://github.com/python/cpython/blob/master/Include/code.h . Значення 67 для flagsаргументу є, наприклад 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) нашої функції до стеку і argcдорівнює 1, ми піднімемо xif, якщо це випадок винятку, або зробимо екземпляр 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)

Запустивши його, ми отримаємо такий же результат, як і раніше. Це просто коротше.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.