Почему присваивания не разрешены в лямбда-выражениях Python?

Это не дубликат Assignment внутри лямбда-выражения в Python, т. е. я я не спрашиваю, как заставить Python присваивать значения в выражении lambda.

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

#!/usr/bin/python

def applyTo42(f):
    return f(42)

def double(x):
    return x * 2

class ContainsVal:
    def __init__(self, v):
        self.v = v

    def store(self, v):
        self.v = v

def main():

    print('== functional, no side effects')

    print('-- print the double of 42')
    print(applyTo42(double))

    print('-- print 1000 more than 42')
    print(applyTo42(lambda x: x + 1000))

    print('-- print c\'s value instead of 42')
    c = ContainsVal(23)
    print(applyTo42(lambda x: c.v))


    print('== not functional, side effects')

    print('-- perform IO on 42')
    applyTo42(lambda x: print(x))

    print('-- set c\'s value to 42')
    print(c.v)
    applyTo42(lambda x: c.store(x))
    print(c.v)

    #print('== illegal, but why?')
    #print(applyTo42(lambda x: c.v = 99))

if __name__ == '__main__':
    main()

Но если я раскомментирую строки

    print('== illegal, but why?')
    print(applyTo42(lambda x: c.v = 99))

Я получу

SyntaxError: lambda cannot contain assignment

Почему бы нет? Какова более глубокая причина этого?

  • Как показывает код, речь не может идти о «чистоте» в функциональном смысле.

  • Единственное объяснение, которое я могу себе представить, это то, что assignemts ничего не возвращают, даже None. Но это звучит неубедительно, и это было бы легко исправить (один из способов: заставить лямбда-выражения возвращать None, если тело является оператором).

Не ответ:

  • Потому что он так определен (я хочу знать, почему он так определен).

  • Потому что это в грамматике (см. выше).

  • Используйте def, если вам нужны операторы (я не спрашивал, как получить операторы в функцию).

«Это изменит синтаксис/язык/семантику» подойдет в качестве ответа, если вы можете привести пример такого изменения и почему это будет плохо.


person stefan    schedule 29.04.2018    source источник
comment
Потому что lambdas преднамеренно ограничены только выражениями. Если вам нужны все остальные, просто используйте полное определение функции. Вот спецификация: docs.python.org/3/reference/expressions.html. #лямбда   -  person juanpa.arrivillaga    schedule 29.04.2018
comment
Рациональное использование присваиваний в виде инструкции, а не выражения, заключается в том, что опечатка, такая как if x = 3: (вместо if x == 3:), становится синтаксической ошибкой, а не тонкой семантической ошибкой времени выполнения.   -  person chepner    schedule 29.04.2018
comment
См. это объяснение: effbot.org/pyfaq/why- can-t-lambda-forms-contain-statements.htm   -  person juanpa.arrivillaga    schedule 29.04.2018
comment
Однако для вашего конкретного примера есть лазейка: lambda x: setattr(c, 'v', 99).   -  person chepner    schedule 29.04.2018
comment
это звучит неубедительно, и это было бы легко исправить - только если вы считаете это изменение исправлением. Изменение языка, чтобы разрешить что-то новое, не всегда выгодно.   -  person user2357112 supports Monica    schedule 29.04.2018
comment
Вам нужно будет взвесить сложности, которые это внесет в грамматику, и преимущества разрешения оператора присваивания в лямбда-выражении. Лямбда-выражения не дают вам возможности делать что-либо, чего вы не могли бы сделать с функцией, определенной с помощью оператора def, и фактически представляют собой удобство, которое было почти полностью исключено из языка во время разработки Python 3.   -  person chepner    schedule 29.04.2018
comment
Ни один из приведенных выше комментариев не полезен для вопроса. Некоторые, возможно, даже не прочитали мой вопрос полностью.   -  person stefan    schedule 30.04.2018
comment
Вот простое решение, поскольку вы недовольны всеми решениями: идите и прочитайте исходный код. Большинство из нас согласится с тем, что язык именно такой, но есть обходной путь, и двигаться дальше.   -  person rmilletich    schedule 11.05.2018


Ответы (7)


Вся причина существования lambda заключается в том, что это выражение.1 Если вам нужно что-то похожее на lambda, но являющееся оператором, это просто def.

Выражения Python не могут содержать операторов. Это, по сути, фундаментально для языка, и Python получает большую выгоду от этого решения. Это причина, по которой отступы для управления потоком работают, а не неуклюжи, как во многих других попытках (например, CoffeeScript). Это причина, по которой вы можете считывать изменения состояния, просматривая первый объект в каждой строке. Это даже одна из причин, по которой язык легко анализируется как для компилятора, так и для читателей2.

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

Изменение Python для создания большинства выражений операторов (например, Ruby) снова превратило бы его в совершенно другой язык без текущих преимуществ Python.

И если бы Python внес любое из этих изменений, то в первую очередь не было бы причин для lambda;2,3 вы могли бы просто использовать операторы def внутри выражение.


Как насчет того, чтобы изменить Python, чтобы вместо этого создавать выражения присваивания? Что ж, должно быть очевидно, что это сломает «вы можете прочитать изменения состояния, просматривая первый объект в каждой строке». Хотя обычно Гвидо акцентирует внимание на том, что if spam=eggs чаще является ошибкой, чем полезной вещью.

Тот факт, что Python дает вам способы обойти это, когда это необходимо, например, setattr или даже явный вызов __setitem__ для globals(), не означает, что это то, что должно иметь прямую синтаксическую поддержку. Что-то, что требуется очень редко, не заслуживает синтаксического сахара — и тем более для чего-то достаточно необычного, что должно вызывать недоумение и/или красные флаги, когда оно действительно сделано.


1. Я понятия не имею, было ли это понимание Гвидо, когда он первоначально добавил lambda обратно в Python 1.0. Но это определенно причина, по которой lambda не был удален в Python 3.0.

2. На самом деле, Гвидо несколько раз предполагал, что разрешение анализатора LL(1), который люди могут запускать в своей голове, является достаточной причиной для того, чтобы язык был основан на операторах, до такой степени, что другие преимущества даже не нужно обсуждать. . Я писал об этом несколько лет назад, если кому-то интересно.< /под>

3. Если вам интересно, почему во многих языках есть выражение lambda, несмотря на то, что def уже есть: во многих языках, от C++ до Ruby, функции не являются первоклассными объектами, которые можно передавать, поэтому им пришлось изобрести вторую вещь, которая была бы первоклассной, но работала бы как функция. В других, от Smalltalk до Java, функции даже не существуют, а только методы, так что им снова пришлось изобретать вторую вещь, которая не являлась бы методом, но работала бы как одна. У Python нет ни одной из этих проблем.

4. Несколько языков, таких как C# и JavaScript, на самом деле имели прекрасно работающие определения встроенных функций, но добавили некоторый синтаксис lambda в качестве чистого синтаксического сахара, чтобы сделать его более кратким и менее шаблонным. Возможно, это действительно стоило бы сделать в Python (хотя все попытки создать хороший синтаксис до сих пор не увенчались успехом), но это не будет текущий синтаксис lambda, который почти так же многословен, как def.

person abarnert    schedule 29.04.2018
comment
У меня складывается впечатление, что вы хотите сказать мне что-то, чего я еще не понимаю. Может быть, вы могли бы уточнить, как резко изменится или даже сломается язык, если операторы будут разрешены в лямбда-выражении (которое тогда, например, вернет None). - person stefan; 30.04.2018
comment
«вы можете прочитать изменения состояния, просматривая первый объект в каждой строке» — неправда. Это означало бы, что только один объект может изменить состояние в каждой строке. - person stefan; 30.04.2018
comment
@stefan Это верно для идиоматического Python: в подавляющем большинстве операторов только элементы слева от = назначаются в операторе присваивания, или первый объект в операторе выражения имеет одну операцию мутации. Конечно, вы можете нарушить это, написав выражение, полное дюжины вызовов setattr, если хотите. Или вы можете поместить девять утверждений в одну огромную строку с точкой с запятой. Python не делает невозможным написание кода, которому трудно следовать; это просто позволяет легко писать код, которому нетрудно следовать, и поощряет это делать с помощью самоуверенных идиом. - person abarnert; 01.05.2018
comment
fileHandle.write(stack.pop()) трудно следить? Но два изменения состояния. - person stefan; 01.05.2018

Есть проблема с синтаксисом: присваивание — это инструкция, а тело лямбда-выражения может иметь только выражения. Синтаксис Python устроен таким образом1. Ознакомьтесь с ним на странице https://docs.python.org/3/reference/grammar.html. .

Существует также проблема семантики: что возвращает каждый оператор?

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

Это также можно исправить, выборочно разрешив определенные операторы в теле лямбды и указав семантику (например, присваивание возвращает None или возвращает присвоенное значение; последнее мне кажется более понятным). Но какая польза?

Лямбды и функции взаимозаменяемы. Если у вас действительно есть вариант использования определенного оператора в теле лямбды, вы можете определить функцию, которая его выполняет, и ваша конкретная проблема будет решена.


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

Например, MacroPy позволит вам определить макрос, который преобразует f[_ * _] в lambda a, b: a * b, поэтому не должно быть невозможно определить синтаксис для лямбды, которая вызывает определенную вами функцию.


1 Хорошая причина не менять его состоит в том, что это нанесет вред синтаксису, потому что лямбда может быть там, где могут быть выражения. И заявлений не должно. Но это мое очень субъективное замечание.

person fferri    schedule 29.04.2018
comment
Я думаю, что ваша сноска - это не просто субъективное замечание, это весь смысл lambda, существующего в первую очередь (и не удаляемого в 3.0). - person abarnert; 29.04.2018
comment
«Так устроен синтаксис Python» — очевидно, так оно и есть, но почему? Что бы иначе сломалось? - person stefan; 30.04.2018
comment
— Но какая польза? — Передача обратного вызова, который просто присваивает аккумулятору: foobar(lambda x: acc = x). - person stefan; 30.04.2018

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

Если бы присваивание было разрешено в лямбда-выражениях, то ошибка смешения == (проверка на равенство) с = (присваивание) имела бы больше шансов уйти в дикую природу.

Пример:

>>> # Correct use of equality test
... list(filter(lambda x: x==1, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[1, 1.0, (1+0j)]

>>> # Suppose that assignment is used by mistake instead of equality testing
... # and the return value of an assignment expression is always None
... list(filter(lambda x: None, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[]

>>> # Suppose that assignment is used by mistake instead of equality testing
... # and the return value of an assignment expression is the assigned value
... list(filter(lambda x: 1, [0, 1, 0.0, 1.0, 0+0j, 1+0j]))
[0, 1, 0.0, 1.0, 0j, (1+0j)]
person Leon    schedule 05.05.2018

Пока exec()eval()) разрешены внутри lambda, вы можете выполнять назначения внутри lambda:

q = 3

def assign(var_str, val_str):
    exec("global " + var_str + "; " + 
    var_str + " = " + val_str)

lambda_assign = lambda var_str, val_str: assign(var_str, val_str)

q ## gives: 3

lambda_assign("q", "100")

q ## gives: 100

## what would such expression be a win over the direct:

q = 100

## ? `lambda_assign("q", "100")` will be for sure slower than
##   `q = 100` isn't it?

q_assign = lambda v: assign("q", v)

q_assign("33")

q ## 33

## but do I need lambda for q_assign?

def q_assign(v): assign("q", v) 

## would do it, too, isn't it?

Но поскольку лямбда-выражения позволяют определить только одно выражение внутри своего тела (по крайней мере, в Python...), какой смысл разрешать присваивание внутри лямбда-выражения? Его чистый эффект будет заключаться в прямом назначении (без использования лямбда-выражения) q = 100, не так ли?

Это было бы даже быстрее, чем делать это с определенной лямбдой, поскольку у вас есть по крайней мере одна функция поиска и выполнения меньше для выполнения...

person Gwang-Jin Kim    schedule 12.05.2018
comment
Может быть полезно установить переменную при срабатывании сигнала. Я использую pyQt и меняю переменные времени выполнения, когда пользователь что-то меняет в пользовательском интерфейсе. - person brazoayeye; 17.11.2018

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

ЕСЛИ в этой истории есть что-то еще, я предполагаю, что МОЖЕТ БЫТЬ, потому что python bdfl GVR выразил свою неприязнь к лямбда-выражениям и другим функциональным функциям и попытался (и уступил) полностью удалить их из python 3 https://www.artima.com/weblogs/viewpost.jsp?thread=98196

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

Как вы сами сказали, дело определенно не в побочных эффектах или чистоте, они просто не хотят, чтобы лямбда была чем-то большим, чем одно выражение... ... ...

С учетом сказанного, вот кое-что о назначениях множественных выражений в лямбда, читайте дальше, если вам интересно

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

редактировать:

пример кода

f = lambda x,a=1: (lambda c = a+2, b = a+1: (lambda e = x,d = c+1: print(a,b,c,d,e))())()

f("w")

# output 1 2 3 4 w

# expression assignment through an object's method call

if let(a=1) .a > 0 and let(b=let.a+1) .b != 1 and let(c=let.b+let.a) .c:
    print(let.a, let.b, let.c)

# output 1 2 3
person guramarx    schedule 07.05.2018

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

Разработчики ядра Python в настоящее время обсуждают PEP 572, который представит выражение привязки имени< /а>.

person holdenweb    schedule 29.04.2018
comment
Однако PEP-572 здесь не поможет, потому что предложенная там привязка имен local; вы не сможете установить глобальные переменные. - person chepner; 29.04.2018

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

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

>>> Celsius = [39.2, 36.5, 37.3, 37.8] 
>>> Fahrenheit = map(lambda x: (float(9)/5)*x + 32, Celsius) # mapping the list here  
>>> print Fahrenheit
[102.56, 97.700000000000003, 99.140000000000001, 100.03999999999999]

Пожалуйста, посетите эту веб-страницу, это может быть полезно. Так держать !!! https://www.python-course.eu/lambda.php

person Jonas Amara    schedule 10.05.2018