How do chained assignments work?
They will not necessarily work the same if somefunction
returns a mutable value. Consider:
>>> def somefunction():
... return []
...
>>> x = y = somefunction()
>>> x.append(4)
>>> x
[4]
>>> y
[4]
>>> x = somefunction(); y = somefunction()
>>> x.append(3)
>>> x
[3]
>>> y
[]
Why does chained assignment work this way?
Per the language docs on assignment:
An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.
In this case, a = a[1:] = [2]
has an expression list [2]
, and two "target lists", a
and a[1:]
, where a
is the left-most "target list".
You can see how this behaves by looking at the disassembly:
>>> import dis
>>> dis.dis('a = a[1:] = [2]')
1 0 LOAD_CONST 0 (2)
2 BUILD_LIST 1
4 DUP_TOP
6 STORE_NAME 0 (a)
8 LOAD_NAME 0 (a)
10 LOAD_CONST 1 (1)
12 LOAD_CONST 2 (None)
14 BUILD_SLICE 2
16 STORE_SUBSCR
18 LOAD_CONST 2 (None)
20 RETURN_VALUE
(The last two lines of the disassembly can be ignored, dis
is making a function wrapper to disassemble the string)
The important part to note is that when you do x = y = some_val
, some_val
is loaded on the stack (in this case by the LOAD_CONST
and BUILD_LIST
), then the stack entry is duplicated and assigned, from left to right, to the targets given.
So when you do:
a = a[1:] = [2]
it makes two references to a brand new list
containing 2
, and the first action is a STORE
one of these references to a
. Next, it stores the second reference to a[1:]
, but since the slice assignment mutates a
itself, it has to load a
again, which gets the list
just stored. Luckily, list
is resilient against self-slice-assignment, or we'd have issues (it would be forever reading the value it just added to add to the end until we ran out of memory and crashed); as is, it behaves as a copy of [2]
was assigned to replace any and all elements from index one onwards.
The end result is equivalent to if you'd done:
_ = [2]
a = _
a[1:] = _
but it avoids the use of the _
name.
To be clear, the disassembly annotated:
Make list [2]
:
1 0 LOAD_CONST 0 (2)
2 BUILD_LIST 1
Make a copy of the reference to [2]
:
4 DUP_TOP
Perform store to a
:
6 STORE_NAME 0 (a)
Perform store to a[1:]
:
8 LOAD_NAME 0 (a)
10 LOAD_CONST 1 (1)
12 LOAD_CONST 2 (None)
14 BUILD_SLICE 2
16 STORE_SUBSCR
How does this chain assignment work?
On line 3 you have a chained assignment
t = A[t] = A.count(3)
t = A.count(3)
is evaluated first – t
set to the return value of A.count(3)
which in this case is 1.
Then the member of A
at index t
(=1) is set to the return value of A.count(3)
, which is still 1.
Read more about chained assignments in Python here
Chained assignment for mutable types
It's useful for simple, common initializations like
foo = bar = baz = 0
so you don't have to write
foo = 0
bar = 0
baz = 0
Since it's a syntax feature, it's not really feasible to make it only work for immutable types. The parser can't tell whether the expression at the end will be a mutable or immutable type. You can have
def initial_value():
if random.choice([True, False]):
return []
else:
return 0
foo = bar = baz = initial_value()
initial_value()
can return a mutable or immutable value. The parser for the assignment can't know what it will be.
There are lots of ways to shoot yourself in the foot with multiple references to mutable values, Python doesn't go out of its way to stop you. For some of the more common examples, see "Least Astonishment" and the Mutable Default Argument and List of lists changes reflected across sublists unexpectedly
You just have to remember that in a chained assignment, the value expression is only evaluated once. So your assignment is equivalent to
temp = [1, 2, 3]
foo = temp
bar = temp
rather than
foo = [1, 2, 3]
bar = [1, 2, 3]
See How do chained assignments work?
A more general rule to remember is that Python never makes copies of objects spontaneously, you always have to tell it to do so.
What is this kind of assignment in Python called? a = b = True
It's a chain of assignments and the term used to describe it is...
- Could I get a drumroll please?
Chained Assignment.
I just gave it a quite google run and found that there isn't that much to read on the topic, probably since most people find it very straight-forward to use (and only the true geeks would like to know more about the topic).
In the previous expression the order of evaluation can be viewed as starting at the right-most =
and then working towards the left, which would be equivalent of writing:
b = True
a = b
The above order is what most language describe an assignment-chain, but python does it differently. In python the expression is evaluated as this below equivalent, though it won't result in any other result than what is previously described.
temporary_expr_result = True
a = temporary_expr_result
b = temporary_expr_result
Further reading available here on stackoverflow:
- How do chained assignments work? python
Why does the order matter in this chained assignment?
You basically answered your question at the end.
node = tmp
node[ch] = tmp
and
node[ch] = tmp
node = tmp
are not equivalent. As soon as you do node = tmp
, you lose the previous contents of node
, and replace them with a new dict. That means that in the first case, you immediately lose the reference to trie
inside the loop, and can no longer mutate it. In the second case, you alter the old results, then reassign node
to the new dictionary.
Assignment operator chain understanding
a += a
means a = a + a
.
likewise, a -= a
means a = a - a
.
I am not sure which way is proper to start, but if I convert the given code from the right using above,
a += a > a = a + a;
a -= a += a > a = a - (a + a);
a+= a -= a += a > a = a + (a - (a + a ));
a -= a+= a -= a += a > a = a - (a + (a - (a + a)));
a = a -= a+= a -= a += a > a = a - a - a + a + a;
where -a -a + a + a
cancels each other, resulting a = a
, which is 10
.
Pandas: Chained assignments
The point of the SettingWithCopy
is to warn the user that you may be doing something that will not update the original data frame as one might expect.
Here, data
is a dataframe, possibly of a single dtype (or not). You are then taking a reference to this data['amount']
which is a Series, and updating it. This probably works in your case because you are returning the same dtype of data as existed.
However it could create a copy which updates a copy of data['amount']
which you would not see; Then you would be wondering why it is not updating.
Pandas returns a copy of an object in almost all method calls. The inplace
operations are a convience operation which work, but in general are not clear that data is being modified and could potentially work on copies.
Much more clear to do this:
data['amount'] = data["amount"].fillna(data.groupby("num")["amount"].transform("mean"))
data["amount"] = data['amount'].fillna(mean_avg)
One further plus to working on copies. You can chain operations, this is not possible with inplace
ones.
e.g.
data['amount'] = data['amount'].fillna(mean_avg)*2
And just an FYI. inplace
operations are neither faster nor more memory efficient. my2c they should be banned. But too late on that API.
You can of course turn this off:
pd.set_option('chained_assignment',None)
Pandas runs with the entire test suite with this set to raise
(so we know if chaining is happening) on, FYI.
Related Topics
How to Make a Call to an Executable from Python Script
How to Fake Terminal Input with Termios.Tiocsti
Best Way to Join/Merge by Range in Pandas
How to Make Separator in Pandas Read_CSV More Flexible Wrt Whitespace, for Irregular Separators
Conda Reports Packagesnotfounderror: Python=3.1 for Reticulate Environment
Placing Custom Images in a Plot Window--As Custom Data Markers or to Annotate Those Markers
Why Is the Subprocess.Popen Argument Length Limit Smaller Than What the Os Reports
Move Files from One Directory to Another with Paramiko
Detect New or Modified Files with Python
How I Open Remote Server Folder Using Python
How to Pass a List as a Command-Line Argument with Argparse
How to Get List of Methods in a Python Class
Why Is Plotting with Matplotlib So Slow