Немає багатолінійної лямбда в Python: чому б і ні?


335

Я чув, як говорилося, що багаторядкові лямбдати не можна додавати в Python, оскільки вони будуть синтаксично зіткнутися з іншими синтаксичними конструкціями в Python. Я сьогодні думав про це в автобусі сьогодні і зрозумів, що не можу придумати жодної конструкції Python, з якою стикаються багатолінійні лямбда. З огляду на те, що я досить добре знаю мову, це мене здивувало.

Тепер я впевнений, що у Гвідо була причина того, що не включати в мову багатолінійних лямбда, але з цікавості: яка ситуація, коли включити багаторядну лямбда було б неоднозначно? Це те, що я чув, правда, чи є якась інша причина, що Python не дозволяє багаторядкові лямбда?


12
tl; dr версія: тому що Python - це ледача мова без {} блоків, і це не було дозволено для збереження послідовної синтаксичної конструкції.
Андрій

11
Також: Я повністю здивований, що ніхто не згадував про це у відповідях ... Ви можете закінчити рядки символом \ у Python та продовжувати переходити до наступного рядка ... Ця інформація ніби витіснила все це питання так ...
Ендрю


"синтаксичне оформлення"
Ніколас

Для цього потрібно дозволити висловлювання всередині виразів. Якщо ви збираєтеся це робити, вам не потрібно lambdaв першу чергу вирази; ви можете просто використовувати defзаяви у виразах.
чепнер

Відповіді:


153

Подивіться на наступне:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

Це лямбда, що повертається (y, [1,2,3])(таким чином карта отримує лише один параметр, що призводить до помилки)? Або повертаєтьсяy ? Або це синтаксична помилка, оскільки кома в новому рядку неправильна? Як би Python знав, чого ти хочеш?

У паренах відступ не має значення для пітона, тому ви не можете однозначно працювати з багатомовними.

Це просто простий, мабуть, більше прикладів.


107
вони можуть змусити використовувати дужки, якщо ви хочете повернути кортеж з лямбда. ІМО, це завжди повинно було застосовуватися для запобігання подібних неясностей, але добре.
mpen

26
Це проста неоднозначність, яку слід вирішити, додавши додатковий набір паролів, що вже існує в багатьох місцях, наприклад, вирази генератора, оточені іншими аргументами, викликаючи метод на цілому літералі (хоча це не повинно бути випадком, оскільки Назва функції не може починатися з цифри), і, звичайно, однорядкові лямбдати (це можуть бути довгі вирази, записані в декількох рядках). Багаторядкові лямбдахи не особливо відрізнятимуться від цих випадків, що вимагає виключення їх на цій основі. Це справжня відповідь.
nmclean

3
Мені подобається, як існують газильйонні мови, які з цим не хвилюються, але якось є глибокі причини, чому це нібито дуже важко, якщо не неможливо
Ніколас

1
@nicolas that python у двох словах
javadba

Причина, чому я не використовую лямбда, тому недостатньо розвинений в Python.
NoName

635

На це точне запитання відповідає сам Гідо ван Россум (винахідник Пітона) у старій публікації блогу .
В основному, він визнає, що це теоретично можливо, але будь-яке запропоноване рішення було б непітонічним:

"Але для мене складність будь-якого запропонованого рішення для цієї головоломки величезна: вона вимагає, щоб аналізатор (а точніше лексема) мав змогу перемикатися вперед і назад між режимами, чутливими до відступу та невідчутливими до відступу, зберігаючи стек попередніх режимів і рівня відступу. Технічно це все можна вирішити (вже є степка рівнів відступів, яку можна було б узагальнити). Але жодне з цього не забирає мою кишку, відчуваючи, що це все досконала контрабанда Руба Голдберга ".


108
Чому це не найкраща відповідь? Йдеться не про технічні причини, а про вибір дизайну, як чітко заявив винахідник.
Дан Абрамов

13
@DanAbramov, оскільки ОП, ймовірно, не входило роками.
Контракт професора Фолкена порушив

7
Для тих, хто не зрозумів посилання на Руба Гольдберга, дивіться: en.wikipedia.org/wiki/Rube_Goldberg_Machine
fjsj

56
Відповідь Гвідо - це ще одна причина, за якою я б хотів, щоб Python не залежав від відступу для визначення блоків.
LS

25
Я не впевнений, що я б назвав "почуття кишки" вибором дизайну. ;)
Елліот Камерон

54

Це, як правило, дуже потворно (але іноді альтернативи є ще більш некрасивими), тому вирішення проблеми полягає в тому, щоб зробити дужки виразом:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

Він не приймає жодних завдань, тому вам доведеться заздалегідь підготувати дані. Я знайшов це корисним місцем - обгортку PySide, де іноді є короткі зворотні дзвінки. Написання додаткових функцій членів було б ще потворніше. Зазвичай вам це не знадобиться.

Приклад:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())

2
Мій начальник просто запитував щось подібне в нашій програмі PyQt. Дивовижно!
TheGerm

1
Дякую за це, я також шукав хороший спосіб використовувати короткі (але все ще багатолінійні) лямбдати як зворотні дзвінки для нашого інтерфейсу PySide.
Майкл Леонард

І тепер я бачив це, миттєво пропонував використовувати lambda argта setattr(arg, 'attr','value')підривати "немає завдань ...". А потім є оцінка короткого замикання andі or... це робить Javascript. Тоне коріння в тобі, як плющ у стіну. Я майже сподіваюся, що я забуду це через Xmas.
nigel222

досить розумний - і досить читабельний. Тепер - про ті (зниклі ..) завдання ..
javadba

@ nigel222 Чому соромно? Мова Пітон принципово каліка - але це одна використовується в будь-якому випадку для більшої частини науки даних . Тож ми робимо корективи. Пошук способів виконання побічних ефектів (досить часто просто друк / ведення журналів!) Та призначення (досить часто просто проміжних варіантів!) Повинні добре оброблятись мовою. Але вони навіть не підтримуються (принаймні, якщо ви дотримуєтесь PEPінструкцій)
javadba

17

Пара відповідних посилань:

Деякий час я слідкував за розвитком Reia, який спочатку збирався мати синтаксис Python на відступі з блоками Ruby, і все це поверх Erlang. Але дизайнер закінчився чутливістю до відступів, і цей пост, який він написав про це рішення, включає обговорення проблем, з якими він зіткнувся з відступом + багаторядкові блоки, і підвищену оцінку, яку він здобув щодо дизайнерських питань / рішень Guido:

http://www.unlimitednovelty.com/2009/03/indentation-sensibility-post-mortem.html

Крім того, ось цікава пропозиція для блоків у стилі Ruby в Python I наткнулася там, де Guido публікує відповідь, фактично не збиваючи її (не впевнений, чи не було наступних збитків):

http://tav.espians.com/ruby-style-blocks-in-python.html


12

[Редагувати] Прочитайте цю відповідь. Це пояснює, чому багаторядкова лямбда - це не річ.

Простіше кажучи, це непітонічно. З публікації блогу Гідо ван Россума:

Я вважаю неприйнятним будь-яке рішення, яке вбудовує блок на основі відступу посередині виразу. Оскільки я вважаю альтернативним синтаксис для групування висловлювань (наприклад, дужки або ключові слова початку / кінця) однаково неприйнятними, це в значній мірі робить багаторядковий лямбда нерозв'язною головоломкою.


10

Дозвольте представити вам славний, але жахливий хак:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

Тепер ви можете використовувати цю LETформу як таку:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

що дає: [0, 3, 8]


1
Спочатку розміщено на gist.github.com/divs1210/d218d4b747b08751b2a232260321cdeb
divs1210

Це круто! Я думаю, що використаю це наступного разу, коли напишу Python. Я в першу чергу програміст Lisp та JS, і відсутність багатолінійної ламбади шкодить. Це спосіб отримати це.
Крістофер Дюма

7

Я винен, що практикую цей брудний злом у деяких своїх проектах, що трохи простіше:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

Я сподіваюся, що ви зможете знайти спосіб пітоніки, але якщо вам доведеться зробити це менш болісно, ​​ніж використовувати exec та маніпулювати глобалами.


6

Дозвольте спробувати вирішити проблему розбору @balpha. Я б скористався дужками навколо багаторядкової ламди. Якщо немає дужок, визначення лямбда є жадібним. Так лямбда в

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

повертає функцію, яка повертається (y*z, [1,2,3])

Але

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

засоби

map(func, [1,2,3])

де func - це багатолінійна лямбда, яка повертається y * z. Це працює?


1
Я думаю, що верхній повинен повернутися, map(func, [1,2,3])а нижній - помилка, оскільки у функції карти недостатньо аргументів. Також в коді є кілька додаткових дужок.
Самі Бенчеріф

потрапляючи в pycharm, що працює python2.7.13, це дає синтаксичну помилку.
simbo1905

додаткові дужки
Самі Бенчеріф

4

(Для всіх, хто все ще цікавиться темою.)

Врахуйте це (включає навіть використання зворотних значень висловлювань у подальших висловлюваннях в межах "багаторядкової" лямбда, хоча це некрасиво до точки блювоти ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12

Це не працює, коли викликається вдруге з різними параметрами і спричиняє витік пам'яті, якщо перший рядок не є, state.clear()оскільки аргументи за замовчуванням створюються лише один раз при створенні функції.
Меттью Д. Скоулфілд

1

Ви можете просто використовувати slash ( \), якщо у вас є кілька ліній для функції лямбда

Приклад:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30

Питання стосується самого використання більше ніж 1 вираз, а не більше 1 буквального рядка.
Томаш

1

Я починаю з python, але виходячи з Javascript, найбільш очевидний спосіб - це витяг виразу як функції ....

Надуманий приклад, вираз множення (x*2)вилучається як функція, і тому я можу використовувати багаторядкові:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Можливо, це не відповідає точно на запитання, якщо це було, як зробити багаторядковий в самому виразі лямбда , але у випадку, якщо хтось отримає цю тему, шукаючи, як налагодити вираз (як я), я думаю, це допоможе


2
Чому я б це робив, а не просто писав map(multiply, [1, 2, 3])?
тотал

0

Що стосується некрасивих хакерів, ви завжди можете використовувати комбінацію execта звичайну функцію, щоб визначити багатолінійну функцію, як це:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

Ви можете перетворити цю функцію на зразок:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')

0

Я просто трохи пограв, щоб спробувати зрозуміти вислів із зменшенням, і придумав цей зловмисник:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

Я просто намагався зробити те саме, що було зроблено в цьому розумінні диктанту Javascript: https://stackoverflow.com/a/11068265


0

Ось більш цікава реалізація багатолінійних лямбда. Цього досягти неможливо через те, як python використовує відступи як спосіб структурування коду.

Але на щастя для нас, форматування відступів можна відключити за допомогою масивів і дужок.

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

lambda args: (expr1, expr2,... exprN)

Теоретично, якщо ви гарантовано матимете оцінку зліва направо, вона працювала б, але ви все одно втрачаєте значення, передані з одного виразу на інше.

Один із способів досягти того, що є дещо докладнішим, - це мати

lambda args: [lambda1, lambda2, ..., lambdaN]

Де кожна лямбда отримує аргументи від попередньої.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

Цей метод дозволяє вам написати щось таке, що трохи схоже на схему.

Тож ви можете написати такі речі:

let(lambda x, y: x+y)((1, 2))

Більш складний метод може бути використаний для обчислення гіпотенузи

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

Це поверне список скалярних чисел, щоб його можна було використовувати для зменшення кількох значень до одного.

Маючи багато лямбда, це, звичайно, не буде дуже ефективно, але якщо ви обмежені, це може бути хорошим способом швидко зробити щось, а потім перепишіть його як фактичну функцію пізніше.


-3

тому що лямбда-функція повинна бути односкладовою, як її найпростіша форма функції, an entrance, then return


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