Почему Python не допускает многострочные лямбды?

50

Может кто-нибудь объяснить конкретные причины, по которым BDFL решили сделать лямбды Python однострочными?

Это хорошо:

lambda x: x**x

Это приводит к ошибке:

lambda x:
    x**x

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

Посмотрите на JavaScript, например. Как можно жить без этих анонимных функций? Они незаменимы. Разве Pythonistas не хочет избавиться от необходимости называть каждую многострочную функцию просто для передачи ее в качестве аргумента?

treecoder
источник
3
Учитывая, что вы отмечаете конкретные причины, по которым Гвидо не допускает лямбды с несколькими выражениями, а затем отклоняете их, я предполагаю, что вы ищете подтверждение, а не реальный ответ.
Джейсон Бейкер
3
Помимо сохранения семи символов, как это лучше, чем def? Теперь он имеет точно такую ​​же визуальную структуру.
детально
2
stackoverflow.com/questions/1233448/…
Маурисио Шеффер

Ответы:

42

Гидо ван ван Россум сам ответил на это:

Но таким решениям часто не хватает «Pythonicity» - этой неуловимой черты хорошей возможности Python. Невозможно выразить Pythonicity как жесткое ограничение. Даже Zen of Python не превращается в простой тест Pythonicity ...

В приведенном выше примере легко найти ахиллесову пяту предложенного решения: двойное двоеточие, хотя и является синтаксически однозначным (одно из «ограничений головоломки»), совершенно произвольно и не похоже ни на что другое в Python ...

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

http://www.artima.com/weblogs/viewpost.jsp?thread=147358

По сути, он говорит, что хотя решение возможно, оно не совпадает с тем, как работает Python.

Блэк Джек
источник
2
+1 спасибо за ссылку на ветку - но я все равно оценил бы многострочные лямбды - они неоценимы - посмотрите на JavaScript, в PHP они тоже включены.
древовидный код
2
@greengit Вы можете просто использовать вложенный def. Это не то же самое, что анонимные функции, но они достаточно близки.
Йтернберг
2
вложенные определения не помогают при передаче функций в качестве аргументов - это причина номер один, почему я хотел бы использовать многострочные лямбды
treecoder
1
@greengit - я думаю, вам лучше заняться этим с GvR, чем размещать здесь свои комментарии.
Джейсон Бейкер
9
@greengit: Вы знаете, что можете передать функцию в качестве аргумента другой функции? Вы не можете написать это в строке, но нет такой техники программирования, которая вам недоступна.
Btilly
24

совершенно нормально делать многострочную лямбду в python: смотрите

>>> f = lambda x: (
...   x**x)
>>> f
<function <lambda> at 0x7f95d8f85488>
>>> f(3)
27

настоящее ограничение лямбды заключается в том, что лямбда должна быть одним выражением ; он не может содержать ключевое слово (например, python2 printили return).

GvR выбирает это для ограничения размера лямбды, так как они обычно используются в качестве параметров. Если вы хотите настоящую функцию, используйтеdef

Вито Де Туллио
источник
1
Многострочная строка - это вставка символа '\ n': D Python не имеет лямбда- выражения с несколькими операторами . Вы действительно хотите использовать def. Подумайте об этом: вам действительно нужен вызываемый элемент как параметр вашей функции? И пользователям этой функции не разрешено передавать ваш вызов по умолчанию? Как они могут передать это, если вы не даете это им?
Вито Де Туллио
Кстати, вы можете привести пример вашей потребности в анонимной функции?
Вито Де Туллио
1
Да, я нахожу ограничение одного выражения действительно разочаровывающим. Правда, если они разрешают лямбды с несколькими выражениями, люди наверняка начнут злоупотреблять ею, но наоборот - слишком ограничительный омхо.
rbaleksandar
10

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

Альтернативой использованию лямбды может быть использование defнестандартного способа. Цель состоит в том, чтобы передать defфункцию, которая может быть выполнена только при одном обстоятельстве - декоратор. Обратите внимание, что в этой реализации def resultне создается функция, она создает результат reduce(), который в конечном итоге становится dict.

Бесстыдная вилка : я делаю много здесь .

>>> xs = [('a', 1), ('b', 2), ('a', 3), ('b', 4)]
>>> foldl = lambda xs, initial: lambda f: reduce(f, xs, initial)
>>> @foldl(xs, {})
... def result(acc, (k, v)):
...     acc.setdefault(k, 0)
...     acc[k] += v
...     return acc
...
>>> result
{'a': 4, 'b': 6} 

Обратите внимание, что лямбда-выражения с несколькими утверждениями могут быть выполнены, но только с очень, очень уродливым кодом. Тем не менее, интересно то, как область видимости работает с этой реализацией (обратите внимание на многократное использование nameпеременной и ее затенение message.

>>> from __future__ import print_function
>>> bind = lambda x, f=(lambda x: x): f(x)
>>> main = lambda: bind(
...     print('Enter your name.'), lambda _: bind(
...     raw_input('> '), lambda name: bind(
...     'Hello {}!'.format(name), lambda message: bind(
...     print(message), lambda _: bind(
...     'Bye {}!'.format(name), lambda message: bind(
...     print(message)
... ))))))
>>> main()
Enter your name.
> foo
Hello foo!
Bye foo!
pyrospade
источник
+1 для монадического подхода
jozefg
Монады также называют JavaScript, будущим / обещанием или даже обратным вызовом в JavaScript.
aoeu256
3

Взлом вместе мульти-заявление лямбда не совсем так плохо , как pyrospade разбирает: что мы могли бы составить кучу монадических функций с помощью привязки, как в Haskell, но так как мы находимся в нечистом мире Python, мы могли бы также использовать побочные эффекты, чтобы достичь того же.

Я расскажу о нескольких способах сделать это в своем блоге .

Например, Python гарантирует, что элементы кортежа будут оцениваться по порядку, поэтому мы можем использовать их ,как императив ;. Мы можем заменить много утверждений, например print, на выражения, как sys.stdout.write.

Следовательно, следующее эквивалентно:

def print_in_tag_def(tag, text):
    print "<" + tag + ">"
    print text
    print "</" + tag + ">"

import sys
print_ = sys.stdout.write
print_in_tag_lambda = lambda tag, text: (print_("<" + tag + ">"),
                                         print_(text),
                                         print_("</" + tag + ">"),
                                         None)[-1]

Обратите внимание, что я добавил Noneв конце и извлек его с помощью [-1]; это устанавливает возвращаемое значение явно. Нам не нужно этого делать, но без этого мы получили бы фанки (None, None, None)возвращаемого значения, о котором мы можем заботиться или не заботиться.

Таким образом, мы можем упорядочить действия ввода-вывода. Как насчет локальных переменных?

Python =формирует утверждение, поэтому нам нужно найти эквивалентное выражение. Одним из способов является изменение содержимого структуры данных, передаваемой в качестве аргумента. Например:

def stateful_def():
    foo = 10
    bar = foo * foo
    foo = 2
    return foo + bar

stateful_lambda = (lambda state: lambda *_: (state.setdefault('foo', 10),
                                             state.setdefault('bar', state.get('foo') * state.get('foo')),
                                             state.pop('foo'),
                                             state.setdefault('foo', 2),
                                             state.get('foo') + state.get('bar'))[-1])({})

Есть несколько трюков, которые используются в stateful_lambda:

  • *_Аргумент позволяет нашей лямбда принимать любое количество аргументов. Поскольку это допускает нулевые аргументы, мы восстанавливаем соглашение о вызовах stateful_def.
    • Вызов аргумента _- это просто соглашение, которое гласит: «Я не собираюсь использовать эту переменную»
  • У нас есть одна («обертка») функция, возвращающая другую («основную») функцию: lambda state: lambda *_: ...
    • Благодаря лексической области видимости аргумента первой функции будет в области видимости для второй функции
    • Принятие некоторых аргументов сейчас и возврат другой функции для принятия остальных позже называется каррированием
  • Мы немедленно вызываем функцию «обертка», передавая ей пустой словарь: (lambda state: ...)({})
    • Это позволяет нам присвоить переменную stateзначению {}без использования оператора присваивания (например, state = {})
  • Мы рассматриваем ключи и значения stateкак имена переменных и связанные значения
    • Это менее громоздко, чем использование сразу называемых лямбд
    • Это позволяет нам изменять значения переменных
    • Мы используем state.setdefault(a, b)вместо a = bи state.get(a)вместоa
  • Мы используем кортеж, чтобы связать воедино наши побочные эффекты, как и раньше
  • Мы используем [-1]для извлечения последнего значения, которое действует как returnутверждение

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

# Keeps arguments and values close together for immediately-called functions
callWith = lambda x, f: f(x)

# Returns the `get` and `setdefault` methods of a new dictionary
mkEnv = lambda *_: callWith({},
                            lambda d: (d.get,
                                       lambda k, v: (d.pop(k), d.setdefault(k, v))))

# A helper for providing a function with a fresh `get` and `setdefault`
inEnv = lambda f: callWith(mkEnv(), f)

# Delays the execution of a function
delay = lambda f x: lambda *_: f(x)

# Uses `get` and `set`(default) to mutate values
stateful_lambda = delay(inEnv, lambda get, set: (set('foo', 10),
                                                 set('bar', get('foo') * get('foo')),
                                                 set('foo', 2),
                                                 get('foo') + get('bar'))[-1])
Warbo
источник
ты шутишь, это похоже на кошмар LOL
Александр Миллс
1
@AlexanderMills Хех, это не было задумано как в реальном мире , например, более опровержением pyrospade - х лямбды-в-лямбды-в-лямбды подойти, чтобы показать , что не все, что Баде. На самом деле, теперь это можно упростить гораздо больше, поскольку у нас есть python.org/dev/peps/pep-0572
Warbo
1

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

x = lambda x,y: x-y if x<y \ 
                     else y-x if y<x \
                     else 0

Не забывайте, что Python умеет писать на однослойных компьютерах, как в примере:

a=b=0; c=b+a; d = a+b**2 #etc etc

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

makeTag = lambda tagName: "<{}>".format(tagName)
closeTag = lambda tagName: makeTag("/"+str(tagName))
openTag = lambda tagName: makeTag(tagName)
writeHMTLline = lambda tag,content: ""+opetTag(tag)+str(content)+closeTag(tag)

Но вы действительно хотите сделать это так? Через некоторое время он становится практически нечитаемым, это похоже на начало веревки, начиная с распутанного конца. распутанная веревка

Лямбды представлены как единственные функции в карте, фильтруют и сокращают функции в функционально-ориентированном программировании (среди прочего). Например, получение символьных значений значений, которые являются целыми числами и делятся на 2

chrDev2 = lambda INT: chr(INT) if isinstance(INT,int) and INT%2==0 else INT
someStringList = map( chrDev2, range(30) )
>>> ['\x00', 1, '\x02', 3, '\x04', 5, '\x06', 7, '\x08', 9, '\n', 11, '\x0c', 13, '\x0e', 15, '\x10', 17, '\x12', 19, '\x14', 21, '\x16', 23, '\x18', 25, '\x1a', 27, '\x1c', 29]

Вы можете использовать его как функцию выражения функции, определив сложную функцию (или несколько и более лямбда-выражений, и поместив ее в другую лямбду-выражение:

def someAnon(*args): return sum(list(args))
defAnon = lambda list: [ x*someAnon(*list) for x in list]

но в Python есть поддержка выражений функций по-другому: -lets говорят, что у вас есть вызванная функция, superAwesomeFunctionи эта функция может делать какие-то суперские вещи, вы можете назначить ее переменной, не вызывая ее, например:

SAF = superAwesomeFunction # there is no () at the end, 

Поэтому теперь, когда вы вызываете SAF, вы вызываете superAwesomeFunction или метод. Если вы выполните поиск в папке Lib, вы обнаружите, что большинство __builtin__модулей Python написаны именно так. Это сделано потому, что иногда вам понадобятся некоторые функции, которые выполняют определенную задачу, которая не является необходимой для использования пользователем, но необходима для нескольких функций. Итак, у вас есть выбор: вы не можете иметь 2 функции с именем «superAwesomeFunction», вы можете иметь «superAwesomeFunctionDoingBasicStuf» и «realSuperAwesomeFunction», а затем просто поместить «realSuperAwesomeFunction» в переменную «superAwesomeFunction», и все готово.

Вы можете найти расположение импортированных модулей, введя в консоли importedModule.__file__(реальный пример import os;os.__file__), и просто следуйте по этому каталогу в файл с именем importModule.py, откройте его в редакторе и найдите, как вы можете максимизировать свои собственные «знания».

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

Данило
источник