Why are assignments not allowed in Python's `lambda` expressions?
Asked Answered
A

7

32

This is not a duplicate of Assignment inside lambda expression in Python, i.e., I'm not asking how to trick Python into assigning in a lambda expression.

I have some λ-calculus background. Considering the following code, it looks like Python is quite willing to perform side-effects in lambda expressions:

#!/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()

But if I uncomment the lines

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

I'll get

SyntaxError: lambda cannot contain assignment

Why not? What is the deeper reason behind this?

  • As the code demonstrates, it cannot be about “purity” in a functional sense.

  • The only explanation I can imagine is that assignemts do not return anything, not even None. But that sounds lame and would be easy to fix (one way: make lambda expressions return None if body is a statement).

Not an answer:

  • Because it's defined that way (I want to know why it's defined that way).

  • Because it's in the grammar (see above).

  • Use def if you need statements (I did not ask for how to get statements into a function).

“This would change syntax / the language / semantics” would be ok as an answer if you can come up with an example of such a change, and why it would be bad.

Ailanthus answered 29/4, 2018 at 20:14 Comment(8)
Because lambdas are limited to only expressions on purpose. If you want all the rest, just use a full function definition. Here is the spec: docs.python.org/3/reference/expressions.html#lambdaSquamous
The rational for making assignments a statement, rather than an expression, is so that a typo like if x = 3: (instead of if x == 3:) becomes a syntax error rather than a subtle runtime semantic error.Sweaty
See this explanation: effbot.org/pyfaq/why-can-t-lambda-forms-contain-statements.htmSquamous
There is a loophole for your particular example, though: lambda x: setattr(c, 'v', 99).Sweaty
"that sounds lame and would be easy to fix" - only if you consider that change a fix. Changing the language to allow a new thing isn't always beneficial.Burton
You would have to weigh the complications this would introduce to the grammar against the benefit of allowing an assignment statement in a lambda expression. Lambda expressions don't give you the ability to do anything you couldn't do with a function defined with a def statement, and in fact are a convenience that was almost eliminated from the language altogether during the design of Python 3.Sweaty
None of above comments is useful to the question. Some might not have even read my question completely.Ailanthus
Here's a simple solution since you are unsatisfied with all of the solutions: Go read the source code. Most of us would accept the language is just that way, but there's a workaround, and move on.Chelseychelsie
S
27

The entire reason lambda exists is that it's an expression.1 If you want something that's like lambda but is a statement, that's just def.

Python expressions cannot contain statements. This is, in fact, fundamental to the language, and Python gets a lot of mileage out of that decision. It's the reason indentation for flow control works instead of being clunky as in many other attempts (like CoffeeScript). It's the reason you can read off the state changes by skimming the first object in each line. It's even part of the reason the language is easy to parse, both for the compiler and for human readers.2

Changing Python to have some way to "escape" the statement-expression divide, except maybe in a very careful and limited way, would turn it into a completely different language, and one that no longer had many of the benefits that cause people to choose Python in the first place.

Changing Python to make most statements expressions (like, say, Ruby) would again turn it into a completely different language without Python's current benefits.

And if Python did make either of those changes, then there'd no longer be a reason for lambda in the first place;2,3 you could just use def statements inside an expression.


What about changing Python to instead make assignments expressions? Well, it should be obvious that would break "you can read off the state changes by skimming the first object in each line". Although Guido usually focuses on the fact that if spam=eggs is an error more often than a useful thing.

The fact that Python does give you ways to get around that when needed, like setattr or even explicitly calling __setitem__ on globals(), doesn't mean it's something that should have direct syntactic support. Something that's very rarely needed doesn't deserve syntactic sugar—and even more so for something that's unusual enough that it should raise eyebrows and/or red flags when it actually is done.


1. I have no idea whether that was Guido's understanding when he originally added lambda back in Python 1.0. But it's definitely the reason lambda wasn't removed in Python 3.0.

2. In fact, Guido has, multiple times, suggested that allowing an LL(1) parser that humans can run in their heads is sufficient reason for the language being statement-based, to the point that other benefits don't even need to be discussed. I wrote about this a few years ago if anyone's interested.

3. If you're wondering why so many languages do have a lambda expression despite already having def: In many languages, ranging from C++ to Ruby, function aren't first-class objects that can be passed around, so they had to invent a second thing that is first-class but works like a function. In others, from Smalltalk to Java, functions don't even exist, only methods, so again, they had to invent a second thing that's not a method but works like one. Python has neither of those problems.

4. A few languages, like C# and JavaScript, actually had perfectly working inline function definitions, but added some kind of lambda syntax as pure syntactic sugar, to make it more concise and less boilerplatey. That might actually be worth doing in Python (although every attempt at a good syntax so far has fallen flat), but it wouldn't be the current lambda syntax, which is nearly as verbose as def.

Stearin answered 29/4, 2018 at 20:54 Comment(4)
I get the impression that you want to tell me something I don't yet understand. Maybe you could elaborate on how the language would dramatically change or even break if statements were allowed in a lambda expression (which would then, e.g., return None).Ailanthus
“you can read off the state changes by skimming the first object in each line” — not true. It would imply that only one object could change state in each line.Ailanthus
@Ailanthus It is true in idiomatic Python: in the vast majority of statements, only the things to the left of an = get assigned in an assignment statement, or the first object in an expression statement has a single mutating operation. Of course you can violate that by writing an expression full of a dozen setattr calls if you want. Or you can put nine statements on one huge lines with semicolons. Python doesn’t make it impossible to write code that’s hard to follow; it just makes it easy to write code that’s not hard to follow, and encourages doing so through opinionated idioms.Stearin
fileHandle.write(stack.pop()) hard to follow? But two state changes.Ailanthus
H
8

There is a syntax problem: an assignment is a statement, and the body of a lambda can only have expressions. Python's syntax is designed this way1. Check it out at https://docs.python.org/3/reference/grammar.html.

There is also a semantics problem: what does each statement return?

I don't think there is interest in changing this, as lambdas are meant for very simple and short code. Moreover, a statement would allow sequences of statements as well, and that's not desirable for lambdas.

It could be also fixed by selectively allowing certain statements in the lambda body, and specifying the semantics (e.g. an assignment returns None, or returns the assigned value; the latter makes more sense to me). But what's the benefit?

Lambdas and functions are interchangeable. If you really have a use-case for a particular statement in the body of a lambda, you can define a function that executes it, and your specific problem is solved.


Perhaps you can create a syntactic macro to allow that with MacroPy3 (I'm just guessing, as I'm a fan of the project, but still I haven't had the time to dive in it).

For example MacroPy would allow you to define a macro that transforms f[_ * _] into lambda a, b: a * b, so it should not be impossible to define the syntax for a lambda that calls a function you defined.


1 A good reason to not change it is that it would cripple the syntax, because a lambda can be in places where expressions can be. And statements should not. But that's a very subjective remark of my own.

Hereinto answered 29/4, 2018 at 20:16 Comment(3)
I think your footnote isn't just a subjective remark, it's the whole point of lambda existing in the first place (and not being removed in 3.0).Stearin
“Python's syntax is designed this way” — obviously it is, but why? What would break otherwise?Ailanthus
“But what's the benefit?” — Passing a callback that just assigns to an accumulator: foobar(lambda x: acc = x).Ailanthus
Y
3

My answer is based on chepner's comment above and doesn't draw from any other credible or official source, however I think that it will be useful.

If assignment was allowed in lambda expressions, then the error of confusing == (equality test) with = (assignment) would have more chances of escaping into the wild.

Example:

>>> # 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)]
Yiddish answered 5/5, 2018 at 15:41 Comment(0)
B
2

As long as exec() (and eval()) is allowed inside lambda, you can do assignments inside 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?

But since lambda expressions allow only 1 expression to be defined inside their body (at least in Python ...), what would be the point of to allow an assignment inside a lambda? Its net effect would be to assign directly (without using any lambda) q = 100, isn't it?

It would be even faster than doing it over a defined lambda, since you have at least one function lookup and execution less to execute ...

Boutwell answered 12/5, 2018 at 8:8 Comment(1)
Could be useful to set a variable when a signal is triggered. I'm using pyQt and i change runtime variables when an user change something in the UIInchmeal
G
1

There's not really any deeper reasons, it has nothing to do with lambda or functional language designs, it's just to avoid programmers from mixing = and == operators, which is a very common mistake in other languages

IF there's more to this story, I assume like MAYBE because python bdfl GVR has expressed his unloving sides to lambda and other functional features and attempted(and conceded) to remove them from python 3 altogether https://www.artima.com/weblogs/viewpost.jsp?thread=98196

At the time of this writing the core devs were seen having a heated discussions recently on whether to include a limited name binding expression assignment, the debate is still on going so perhaps someday we may see it in lambda(unlikely)

As you said it yourself it is definitely not about side effects or purity, they just don't want lambda to be more than a single expression... ... ...

With that said, here's something about multi expressions assignments in lambda, read on if you're interested

It is not at all impossible in python, in fact it was sometimes necessary to capture variable and sidestep late bindings by (ab)using kwargs(keyword arguments)

edit:

code example

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
Gwenore answered 7/5, 2018 at 15:39 Comment(0)
K
0

As it stands, Python was designed as a statement-based language. Therefore assignment and other name bindings are statements, and do not have any result.

The Python core developers are currently discussing PEP 572, which would introduce a name-binding expression.

Kettledrum answered 29/4, 2018 at 20:19 Comment(1)
PEP-572 wouldn't help here, though, because the name-binding proposed there is local; you won't be able to set global variables.Sweaty
H
0

I think all the fellows answered this already. We use mostly lambdas function when we just want to:

-create some simple functions that do the work perfectly in a specific place(most of the time hidden inside some other big functions -The lambda function does not have a name -Can be used with some other built-ins functions such as map, list and so forth ...

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

Please visit this webpage , this could be useful.Keep it up !!! https://www.python-course.eu/lambda.php

Hoelscher answered 10/5, 2018 at 9:34 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.