Why isn't x *= 5 + 3 equivalent to x = x * 5 + 3?

Viewed 86

If we let x = 2, and we perform x *= 5 + 3, the result is 16, equivalent to if we had done x *= (5 + 3), rather than x = x * 5 + 3 which would be 13.

I intuitively understand this, but a friend is wondering why x*= A + B isn't equivalent to x = x * A + B, when that is seemingly how x+= A + B works; x = x + A + A.

I'm trying to come up with a satisfying explanation for how things are working under the hood that makes this so, and why it was made to work that way.

3 Answers

The "compiler" knows to evaluate the RHS according to PEMDAS/BODMAS first before multiplying the value on the left, and in fact, just simplifies 5+3 to 8:

dis.dis("x *= 5 + 3")
  1           0 LOAD_NAME                0 (x)
              2 LOAD_CONST               0 (8)     # <-- (5+3) = (8)
              4 INPLACE_MULTIPLY
              6 STORE_NAME               0 (x)
              8 LOAD_CONST               1 (None)
             10 RETURN_VALUE

And doing multiplication before addition:

dis.dis("x = x*5 + 3")
  1           0 LOAD_NAME                0 (x)
              2 LOAD_CONST               0 (5)
              4 BINARY_MULTIPLY
              6 LOAD_CONST               1 (3)
              8 BINARY_ADD
             10 STORE_NAME               0 (x)
             12 LOAD_CONST               2 (None)
             14 RETURN_VALUE

and addition before multiplication

dis.dis("x = 3 + x * 5")
  1           0 LOAD_CONST               0 (3)
              2 LOAD_NAME                0 (x)
              4 LOAD_CONST               1 (5)
              6 BINARY_MULTIPLY
              8 BINARY_ADD
             10 STORE_NAME               0 (x)
             12 LOAD_CONST               2 (None)
             14 RETURN_VALUE

Still results in the BINARY_MULTIPLY instruction of x and 5 before BINARY_ADD with 3

Because of operator precedence. All the <operator>= operators have the same precedence as =, which is lower than the regular arithmetic operators. So

x *= 5 + 3

is parsed as

x *= (5 + 3)

for the same reason that

x = 5 + 3

is parsed as

x = (5 + 3)

rather than

(x = 5) + 3

The last isn't even meaningful in Python, because = can't be used in expressions, only statements. But you could replace it with the := operator to get something analogous to JavaScript.

This has nothing to do with assembly language and everything to do with language definition and parsing to that specification, as required by the language definition.

Your friend is splitting the * and = apart but they are one operator, not two.  As a result there is only a single precedence value for that entire operator.  We can't split these apart and then give each their own precedence!

While this kind of operator is a shortcut of sorts, it is a true operator, not a macro nor a form of textual substitution.


Consider for example, a[i++] += x or a[f()] += x.  While those are legal and ok, they won't necessarily work "expanded" as in a[i++] = a[i++] + x, which is illegal undefined behavior, due to two modifications to i within a single expression.

Related