How does the name of an immutable object rebind to the result of an augmented assignment?
For mutable objects, example, if x = [1, 2, 3]
, and y = [4, 5], then when we do x += y, it is executed as x.__iadd__(y)
which modifies x
in place and does the name x
rebind to it again?
And how does it work when x
is immutable? Here's what Python documentation has to say about augmented assignments.
If x is an instance of a class that does not define a
__iadd__()
method,x.__add__(y)
andy.__radd__(x)
are considered, as with the evaluation ofx + y
.
OK, now if x = (1, 2, 3)
and y = (4, 5)
when we do x += y
, python executes x.__add__(y)
which creates a new object. But when and how does this new object get rebound to x
?
I went spelunking in the source code of CPython specifically the tuple
object (tupleobject.c) and AugAssign
parts in Python-ast.c and ast.c, but couldn't really understand how the rebinding occurs.
EDIT: Removed the misconceptions in the original question and changed them to question form to prevent confusing future readers.
An augmented assignment is generally used to replace a statement where an operator takes a variable as one of its arguments and then assigns the result back to the same variable. A simple example is x += 1 which is expanded to x = x + (1) .
Most python objects (booleans, integers, floats, strings, and tuples) are immutable. This means that after you create the object and assign some value to it, you can't modify that value. Definition An immutable object is an object whose value cannot change.
Immutable Objects : These are of in-built types like int, float, bool, string, unicode, tuple. In simple words, an immutable object can't be changed after it is created. Mutable Objects : These are of type list, dict, set . Custom classes are generally mutable.
You can poke into this area, change it and find that a "has a" different value now. A statement like a = 2 (or a = b where b is another variable) takes the value of the right hand side and writes it into the memory location reserved for a . Now you can modify b as you wish but a will still retain the original value.
A line like
x += y
is actually translated to the equivalent of
x = x.__iadd__(y)
The rebinding always happens, even if x
is mutable. If __iadd__()
is implemented in a way that performs an in-place operation, it needs to return self
, and the name is rebound to the object it pointed to anyway. If __iadd__()
is not implemented, the usual addition methods __add__()
or __radd__()
are used instead, as you already noted in your post.
This is why x += y
inside a function renders x
a local name. (Explanation: In Python, a name is considered local to a function if there is an assignment to this name inside the function. x += y
is considered an assignment to x
, so x
is considered local to a function containing such a line. See this blog post by Eli Bendersky for more information.)
Another example of this behaviour:
t = ([], 1)
t[0] += [1]
results in the error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
but the list is appended anyway. This is because first list.__iadd__()
is called, changing the list in place. Next, t[0] = <list object>
is attempted, resulting in the error.
Detailed documentation on the semantics can be found in the Python Language Reference and in PEP 203. A quote from the former:
An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target. The target is only evaluated once.
An augmented assignment expression 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.
The augmented assignments - +=
*=
and so on are actually assignments, that do bind the left hand names to the return result of the corresponding operator.
So, if you have a class that offers "__iadd__
", and perform obj += expr
on an object of that class, the method obj.__iadd__(expr)
is called and its return value is assigned to the name "obj".
Example:
>>> class A(object):
... def __iadd__(self, other):
... return "fixed value"
...
>>> a = A()
>>> a += ["anything"]
>>> print a
fixed value
BTW, if the object's class don't define the enhanced operators, the regular version is called by Pythonś machinery
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With