Why does Python assignment not return a value?

Why is Python assignment a statement rather than an expression? If it was an expression which returns the value of the right hand side in the assignment, it would have allowed for much less verbose code in some cases. Are there any issues I can't see?

For example:

# lst is some sequence
# X is come class
x = X()
lst.append(x)

could have been rewritten as:

lst.append(x = X())

Well, to be precise, the above won't work because x would be treated as a keyword argument. But another pair of parens (or another symbol for keyword arguments) would have resolved that.


Solution 1:

There are many who feel that having assignments be expressions, especially in languages like Python where any value is allowable in a condition (not just values of some boolean type), is error-prone. Presumably Guido is/was among those who feel that way. The classic error is:

if x = y: # oops! meant to say ==

The situation is also a bit more complicated in Python than it is in a language like C, since in Python the first assignment to a variable is also its declaration. For example:

def f():
    print x

def g():
    x = h()
    print x

In these two functions the "print x" lines do different things: one refers to the global variable x, and the other refers to the local variable x. The x in g is local because of the assignment. This could be even more confusing (than it already is) if it was possible to bury the assignment inside some larger expression/statement.

Solution 2:

Assignment (sub-)expressions (x := y) are supported since Python 3.8 (released Oct. 2019), so you can indeed now rewrite your example as lst.append(x := X()).

The proposal, PEP 572, was formally accepted by Guido in July 2018. There had also been earlier proposals for assignment expressions, such as the withdrawn PEP 379.

Recall that until version 3, print was also a statement rather than an expression.

The statement x = y = z to assign the same value to multiple targets (or rather, multiple target-lists, since unpacking is also permitted) was already supported (e.g. since version 1) but is implemented as a special syntax rather than by chaining successive assignment sub-expressions. Indeed, the order in which the individual assignments are performed is reversed: nested walruses (x := (y := z)) must assign to y before x, whereas x = y = z assigns to x before y (which may be pertinent if you set/assign to the subscripts or attributes of a class that has been overloaded to create some side-effect).

Solution 3:

The real-world answer: it's not needed.

Most of the cases you see this in C are because of the fact that error handling is done manually:

if((fd = open("file", O_RDONLY)) == -1)
{
    // error handling
}

Similarly for the way many loops are written:

while(i++ < 10)
    ;

These common cases are done differently in Python. Error handling typically uses exception handling; loops typically use iterators.

The arguments against it aren't necessarily earth-shattering, but they're weighed against the fact that it simply isn't that important in Python.