Нет многострочной лямбды в Python: почему бы и нет? - PullRequest
275 голосов
/ 05 августа 2009

Я слышал, что в Python нельзя добавить многострочные лямбды, потому что они синтаксически конфликтуют с другими синтаксическими конструкциями в Python.Сегодня я думал об этом в автобусе и понял, что не могу придумать ни одной конструкции Python, с которой сталкиваются многострочные лямбды.Учитывая, что я довольно хорошо знаю язык, это меня удивило.

Теперь я уверен, что у Гвидо была причина не включать многострочные лямбды в язык, но из любопытства: в какой ситуации включение многострочноголямбда была бы неоднозначной?Верно ли то, что я слышал, или есть какая-то другая причина, по которой Python не допускает многострочные лямбды?

Ответы [ 11 ]

558 голосов
/ 05 августа 2009

Гидо ван Россум (изобретатель Python) сам отвечает на этот точный вопрос в старом сообщении в блоге .
По сути, он признает, что это теоретически возможно, но любое предлагаемое решение будет непифоническим:

"Но сложность любого предложенного решения для этой загадки для меня огромна: для этого требуется, чтобы синтаксический анализатор (или, точнее, лексер) мог переключаться между режимами, чувствительными к отступам и нечувствительными к отступам, сохранение стека предыдущих режимов и уровня отступов. Технически это все можно решить (уже есть набор уровней отступов, которые можно обобщить). Но ни одно из этого не лишает меня интуитивного ощущения, что все это сложный Руб Гольдберг хитрое . "

132 голосов
/ 05 августа 2009

Посмотрите на следующее:

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

Является ли это лямбда, возвращающим (y, [1,2,3]) (таким образом, карта получает только один параметр, что приводит к ошибке)? Или он возвращает y? Или это синтаксическая ошибка, потому что запятая в новой строке не на месте? Как Python узнает, что вы хотите?

Внутри паренов отступ для python не имеет значения, поэтому вы не можете однозначно работать с мультилиниями.

Это просто, возможно, есть и другие примеры.

45 голосов
/ 02 декабря 2012

Как правило, это очень уродливо (но иногда альтернативы еще более уродливы), поэтому обходной путь заключается в создании выражения в скобках:

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

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

Пример:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
17 голосов
/ 05 августа 2009

Пара релевантных ссылок:

Некоторое время я следил за разработкой Reia, которая изначально собиралась использовать синтаксис Python, основанный на отступах, с блоками Ruby, все на вершине Erlang. Но дизайнер отказался от чувствительности к отступам, и этот пост, который он написал об этом решении, включает в себя обсуждение проблем, с которыми он столкнулся с отступами + многострочными блоками, и повышенную оценку, которую он получил за проблемы / решения Гвидо по дизайну:

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

Кроме того, вот интересное предложение для блоков в стиле Ruby в Python, с которым я столкнулся, когда Гвидо публикует ответ без его фактического уничтожения (хотя и не уверен, что был какой-либо последующий сбой):

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

11 голосов
/ 06 октября 2014

[Изменить] Читать этот ответ. Объясняет, почему многострочная лямбда не вещь.

Проще говоря, это не пифонично. Из поста Гвидо ван Россума:

Я считаю неприемлемым любое решение, которое встраивает блок на основе отступа в середину выражения. Поскольку я нахожу альтернативный синтаксис для группировки операторов (например, фигурные скобки или ключевые слова begin / end) одинаково неприемлемым, это в значительной степени делает многострочную лямбду неразрешимой загадкой.

Что касается остальной части этого ответа. Либо используйте однострочную 1 лямбду или именованную функцию. Пожалуйста, не используйте exec - я сожалею, что когда-либо предлагал.

1 Вы будете удивлены, что вы можете сделать с одной строкой Python.


Обходной путь для получения многострочных лямбда-функций (расширение ответа skriticos):

(lambda n: (exec('global x; x=4; x=x+n'), x-2)[-1])(0)

Что он делает:

  • Python упрощает (выполняет) каждый компонент кортежа до чтение разделителей.

  • например, lambda x: (functionA(), functionB(), functionC(), 0)[-1] будет выполнять все три функции, хотя единственная информация используется последний элемент в списке (0).

  • Обычно вы не можете назначать или объявлять переменные в списках или кортежах в python, однако, используя функцию exec, вы можете (обратите внимание, что она всегда возвращает: None).

  • Обратите внимание, что если вы не объявите переменную как global, она не будет существовать вне этого exec вызова функции (это верно только для exec функций в операторах lambda).

  • например, (lambda: exec('x=5;print(x)'))() отлично работает без объявления global. Однако (lambda: (exec('x=5'), exec('print(x)')))() или (lambda: (exec('x=5'), x)() нет.

  • Обратите внимание, что все переменные global хранятся в глобальном пространстве имен и будут продолжать существовать после завершения вызова функции. По этой причине это не очень хорошее решение, и его следует избегать, если это вообще возможно. global переменные, объявленные из функции exec внутри лямбда-функции, хранятся отдельно от пространства имен global. (протестировано в Python 3.3.3)

  • [-1] в конце кортежа получает последний индекс. Например, [1,2,3,4][-1] - это 4. Это делается для того, чтобы возвращались только желаемые выходные значения, а не весь кортеж, содержащий None из exec функций и других посторонних значений.

Эквивалентная многострочная функция:

def function(n):
    x = 4
    x = x+n
    return x-2

function(0)

Способы избежать многострочной лямбды:

Рекурсия:

f = lambda i: 1 if i==0 or i==1 else f(i-1)+f(i-2)

Булевы числа - это целые числа:

lambda a, b: [(0, 9), (2, 3)][a<4][b>3]

итераторы:

lambda x: [n**2 for n in x] #Assuming x is a list or tuple in this case
10 голосов
/ 23 июня 2017

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

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]

6 голосов
/ 25 сентября 2013

Позвольте мне попытаться решить проблему разбора @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]))

означает * * +1010

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

где func - многострочная лямбда, возвращающая y * z. Это работает?

4 голосов
/ 12 января 2016

(Для всех, кто еще заинтересован в теме.)

Учитывайте это (включая даже использование возвращаемых значений операторов в дальнейших операторах внутри «многострочной» лямбды, хотя это уродливо до такой степени; -)

>>> 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
3 голосов
/ 25 января 2019

Я виновен в том, что практиковал этот грязный хак в некоторых из моих проектов, который немного проще:

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

Я надеюсь, что вы можете найти способ оставаться питоническим, но если вам придется делать это менее болезненно, чем использовать exec и манипулировать глобальными переменными.

0 голосов
/ 08 февраля 2019

Я просто немного поиграл, чтобы попытаться разобраться в толковании с помощью Reduce, и придумал один взлом лайнера:

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 dict-понимании: https://stackoverflow.com/a/11068265

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...