In this page of Python official reference, there’s the following sentence:
An augmented assignment statement like
x += 1
can be rewritten asx = x + 1
to achieve a similar, but not exactly equal effect. In the
augmented version,x
is only evaluated once.
It seems to suggest that a simple assignment would evaluate x
twice. I wanted to test this, so created the following example code:
X = [print("ttHello X"), 0 ]
X = X + [1]
This actually prints “Hello X” only once, at the first assignment. I’ve tried making X[0]
a function that prints something instead, and it doesn’t work.
Here’s a small example with setters and getters:
class X:
def __init__(self, value:int = 0):
self._value = value
@property
def value(self):
print("HEllo X!")
return self._value
@value.setter
def value(self, value):
self._value = value
Now, I experiment with the code below:
x = X(1)
x.value = x.value +1
x.value
This prints “HEllo X!” twice. I was expecting it to be thrice.
x = X(1)
x.value +=1
x.value
This prints “HEllo X!” twice, the same number of times as with the simple assignment.
Is there a way to check whether Python really evaluates x
twice with a simple assignment?
2
An example where you can see the difference:
d = {None: 0}
d[print('hello')] += 1
d = {None: 0}
d[print('world')] = d[print('world')] + 1
Output (Attempt This Online!):
hello
world
world
Or with your question’s attempt, but using value [1]
instead of 1
and accessing its element:
x = X([1])
x.value[0] = x.value[0] +1
x.value[0]
print()
x = X([1])
x.value[0] +=1
x.value[0]
Now it does print thrice and twice, as the first half does use the getter three times instead of using it two times and using the setter once.
Attempt This Online!
3
You seem to have misunderstood what they mean by
x
is only evaluated once
in that sentence, x
is assumed to be an expression (which can be evaluated)
In the example you wrote, the expression that gets evaluated twice is simply X
, so the only thing that “happens twice” is the name resolution.
Note that this is still not equivalent, as the X = X + [1]
calls the list.__add__
function, which creates an entirely new list and then sets X
to that new list,
whereas X += [1]
calls the list.__iadd__
method which modifies X
inplace (and should therefore be vastly faster in a lot of cases)
1
Maybe during parsing?
Looks like they are the same bytecode:
>>> import dis
>>> def foo():
... x = 0
... x += 1
...
>>> def bar():
... x = 0
... x = x + 1
...
>>> dis.dis(foo)
1 0 RESUME 0
2 2 LOAD_CONST 1 (0)
4 STORE_FAST 0 (x)
3 6 LOAD_FAST 0 (x)
8 LOAD_CONST 2 (1)
10 BINARY_OP 13 (+=)
14 STORE_FAST 0 (x)
16 RETURN_CONST 0 (None)
>>> dis.dis(bar)
1 0 RESUME 0
2 2 LOAD_CONST 1 (0)
4 STORE_FAST 0 (x)
3 6 LOAD_FAST 0 (x)
8 LOAD_CONST 2 (1)
10 BINARY_OP 0 (+)
14 STORE_FAST 0 (x)
16 RETURN_CONST 0 (None)
The only difference is
10 BINARY_OP 13 (+=)
and
10 BINARY_OP 0 (+)
I guess you need to have a look at how they’re implemented
The documentation further gives a good explanation:
An augmented assignment statement like x += 1 can be rewritten as x = x + 1 to achieve a similar, but not exactly equal effect. In the augmented version, x is only evaluated once. Also, when possible, the actual operation is performed in-place, meaning that rather than creating a new object and assigning that to the target, the old object is modified instead.
Unlike normal assignments, augmented assignments evaluate the left-hand side before evaluating the right-hand side. For example, a[i] += f(x) first looks-up a[i], then it evaluates f(x) and performs the addition, and lastly, it writes the result back to a[i].
1