62
votes

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.

6
What does your code example have to do with the question? - Ignacio Vazquez-Abrams
@Ignacio: my mistake. The version edited (@Laurence Gonsalves) is good. - max
"Are there any issues I can't see?" It appears that "much less verbose code" is somehow not a problem. Terse, cryptic code golf seems like an issue. Are you discounting that one for some reason? - S.Lott
@S Lott: I guess I was thinking of simple examples (like the one I gave, which I don't see as too cryptic); I agree that the ability to abuse this might be one of the reasons against this feature. - max
I like this question and other questions like it. I believe the answers and comments are very constructive and helpful. I'm just amazed the closure police hasn't shut it down already as NC ;-) - cfi

6 Answers

48
votes

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.

24
votes

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

15
votes

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.

7
votes

I believe this was deliberate on Guido's part in order to prevent certain classic errors. E.g.

if x = 3: print x

when you actually meant to say

if x == 3: ...

I do agree there are times I wished it would work, but I also miss { and } around a block of code, and that sure isn't going to change.

4
votes
  1. Python's syntax is much less verbose than C's syntax.
  2. It has much more sophisticated scope rules than C.
  3. To use parentheses in every single expression reduces the code readability and python avoids that.

If assigments were expressions, these and many other features would have to be re-worked. For me it is like a deal you have to make in order to have such readable code and useful features. In order to have

if a and (h not in b): ...

rather than

if (a && !(h in b)) { ... }

[not talking about the classic (if a = b:) kind of error.]

0
votes

Getting into taste discussion, or any discussion perceived as such, is generally a waste of time, no matter how good the arguments.

Outside more general principles, my preferred practical reason to have assignment be an expression is to have a readable equivalent of a case statement when I want to match a string with several patterns (extracting sub-patterns), until one succeeds. Maybe someone has a nice solution which I did not see.

Anyway ... why not make everyone happy by making both worlds available by means of an interpretable comment at the beginning of the module file that states explicitly that assignments can be used as expression, if the programmer so desires?

People satisfied with the current situation will not see any change, and will still have their syntax errors detected in the same way.

And people who want to use assignment as expressions will simply have to say so.

I do not see that allowing to use assignments as expressions in an existing program that did not use it (by simply adding the suggested comment above) would change the semantics of that program.

Peace.


Post-sriptum - This is added after the first five paragraphs of section 1 in the discussion below have been posted.

I do not know why Python designers made that choice. Avoidance of the admittedly common error if a=b : ... instead of if a==b : ... is hardly a justification.

First, it could quite easily be avoided with another notation for assignment, such as := or <-, more appropriate since assignment is not symmetrical, and used in some of the early ancestors of Python.

Second, the same problem is tolerated in another context. One can write a = b=c while meaning actually to write a = b==c which is quite different. And the error is not that visible when b and c are large expressions. Furthermore, while it would probably be detected as a type error if the language were statically typed, this is not so in a dynamically typed language like Python (this is actually true of = versus == in all contexts).

This tolerance is all the more surprising that multiple assignment of the form a = b = c is hardly an essential feature of the language, hardly a very useful feature.

It all looks like remnants of early design decisions, some motivated by similarities with existing languages like C or Bash because Python is also a scripting language, but has become much more than that. In other words, it seems more accidental than well thought out design. Hopefully, that is not what people have in mind when talking of pythonicity.

This being said, these are minor syntactic restrictions, however annoying. The language seems much better designed overall (with some mental restriction regarding scope rules, until I make up my mind about its logic).

An interesting aspect of this discussion is that the prohibition of assignment as expression (though it could be solved by a notation change) is also made necessary by the absence of static typing. But it is to be expected that absence of static typing, which is a legitimate design choice, makes a lot of bugs harder to catch. This is a very general observation. Nevertheless, this is the choice made by Python designers. So be it.

But then they can hardly regret that confusion between equality and assignment will be harder to catch. It is only a direct consequence, one of many consequences of their design choice of more flexibility at the expense of error detection. So error detection is a poor excuse for this limitation on assignment.

Regarding the fact that an expression assignment would mix functionnal and imperative style, this is a non-issue. The mixing is already everywhere in the language.

Is there a written rationale for Python that document design choices in general, and the issue discussed here in particular ?