Чому Python не дозволяє багаторядкові лямбда?


50

Чи може хтось пояснити конкретні причини, чому BDFL вирішує зробити лямбда Python єдиною лінією?

Це добре:

lambda x: x**x

Це призводить до помилки:

lambda x:
    x**x

Я розумію, що створення лямбда-багаторядкових якось "порушить" звичайні правила відступу та вимагатиме додати більше винятків, але чи не варто цього переваги?

Наприклад, подивіться на JavaScript. Як можна жити без цих анонімних функцій? Вони незамінні. Хіба Pythonistas не хочуть позбутися необхідності називати кожну багаторядкову функцію просто для передачі її як аргументу?


3
Враховуючи, що ви відзначаєте конкретні причини, чому Guido забороняє лямбди з багатовираженням, а потім відхиляє їх, я припускаю, що ви шукаєте перевірки, а не реальної відповіді.
Джейсон Бейкер

3
Окрім врятування семи символів, як це краще, ніж def? Зараз вона має точно таку ж візуальну структуру.
detly

Відповіді:


42

На це сам відповів Гвідо ван ван Россум:

Але таким рішенням часто не вистачає "Pythonicity" - тієї невловимої риси хорошої функції Python. Висловити Pythonicity як важке обмеження неможливо. Навіть дзен Python не перекладається на просте випробування Pythonicity ...

У наведеному вище прикладі легко знайти ахілесову п’ятку запропонованого рішення: подвійна кишка, хоч і справді синтаксично однозначна (одне з «обмежень головоломки»), абсолютно довільна і не нагадує нічого іншого в Python ...

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

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

В основному, він каже, що хоча рішення можливо, це не відповідає тому, яким є Python.


2
+1 спасибі за посилання на потоки - але я все-таки вдячний б за допомогою багаторядкових лямбда - вони безцінні - подивіться на JavaScript, PHP також включає їх.
treecoder

2
@greengit Ви можете просто використовувати вкладений def. Це не те саме, що анонімні функції, але вони досить близькі.
jsternberg

2
вкладені defs не допомагають при передачі функцій в якості аргументів - це причина номер один, чому мені хотілося б багаторядкових ягнят
treecoder

1
@greengit - Я думаю, що вам краще взятися за це з GvR, ніж публікувати тут свої коментарі.
Джейсон Бейкер

9
@greengit: Ви знаєте, що можете передати цю функцію як аргумент іншій функції? Ви не можете записати це вбудовано, але немає методики програмування, яка б вам не доступна.
btilly

24

ідеально робити багаторядкові лямбда в пітоні: див

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

справжнє обмеження лямбда - це той факт, що лямбда має бути єдиним виразом ; він не може містити ключове слово (наприклад, python2 printабо return).

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


1
мульти лінія йде про вкладиші «\ п» характер: D пітона не має мульти заяви лямбда. Ви дуже хочете використовувати def. Подумайте над цим: вам дійсно потрібен дзвінок як параметр вашої функції? І користувачі цієї функції не можуть передавати ваш стандартний дзвінок? Як вони можуть їх передати, якщо ви не дасте їм їх?
Віто Де Тулліо

btw, чи можете ви навести приклад вашої потреби в анонімній функції?
Віто Де Тулліо

1
Так, я вважаю обмеження одного виразу справді неприємним. Правда, що якщо вони дозволять лямбдам з багатовираженням, люди, безумовно, почнуть зловживати цим, але навпаки - занадто обмежувальний омхо.
rbaleksandar

10

Я знаю, що це дуже давно, але ставити тут як орієнтир.

Альтернативою для використання лямбда може бути використання defнетрадиційного способу. Мета - передати функцію defa, яку можна виконати лише за однієї обставини - декоратора. Зауважте, що за допомогою цієї реалізації def resultне створюється функція, вона створює результат reduce(), який в кінцевому підсумку є a 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!


Монади в JavaScript BTW також називають thenables або майбутніми / обіцянками або навіть зворотними зворотами.
aoeu256

3

Злому разом лямбда з декількома заявами не так вже й погано, як виходить pyrospade: впевнений, що ми могли скласти купу монадичних функцій за допомогою bind, як у 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 *_: ...
    • Завдяки лексичному розмаху , аргумент першої функції буде доступним для другої функції
    • Прийняття деяких аргументів зараз і повернення іншої функції для прийняття решти пізніше відоме як currying
  • Ми негайно викликаємо функцію "обгортка", передаючи їй порожній словник: (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])

Ви жартуєте, це виглядає як кошмар лол
Олександр Міллс

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 вміє писати oneliners, як, наприклад:

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

І лямбда дуже потужна, але вона не призначена для заміни 1 цілої функції, я маю на увазі, що ви можете її зламати (наприклад, запозичення колеги вище):

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 має підтримку виразів функцій по-іншому: -леті говорять, що у вас є якась функція, яка називається, 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 і відкрити його в редакторі та знайти, як ви можете максимально використовувати свої власні знання.

Я сподіваюся, що це допоможе вам і, можливо, іншим колегам у біді.

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