Почему присвоения не допускаются в выражениях "лямбда" Python?

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

У меня есть некоторый фон -calculus. Учитывая следующий код, похоже, что 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\ 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\ 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

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

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

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

Не ответ:

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

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

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

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

Ответ 1

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

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

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

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

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


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

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


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

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

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

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

Ответ 2

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

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

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

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

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


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

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


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

Ответ 3

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

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

Пример:

>>> # 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)]

Ответ 4

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

ЕСЛИ там больше к этой истории, я предполагаю, что MAYBE, потому что 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 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

Ответ 5

Пока 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, не так ли?

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

Ответ 6

Как бы то ни было, Python был разработан как язык, основанный на заявлении. Поэтому присваивание и другие привязки имен являются операциями и не имеют никакого результата.

В настоящее время разработчики ядра Python обсуждают PEP 572, в котором будет представлено выражение, связывающее имя.

Ответ 7

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

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

>>> 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

Ответ 8

Это решение было основано на стремлении дизайнеров Python отбить чрезмерно сложные выражения, но вы правы, это не было фундаментальной причиной того, что его нельзя было поддержать, и практическая ценность его разрешения наконец-то победила! Новый оператор выражения присваивания := поступающий в Python 3.8 в конце 2019 года, будет поддерживать присваивание внутри лямбда-выражений. Вы сможете написать код, подобный следующему:

import sys
say_hello = lambda: (
    message := "Hello world",
    sys.stdout.write(message + "\n")
)[-1]
say_hello()