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


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.