bgneal@4
|
1 A C & Python chained assignment gotcha
|
bgneal@4
|
2 ######################################
|
bgneal@4
|
3
|
bgneal@4
|
4 :date: 2012-12-27 14:45
|
bgneal@4
|
5 :tags: Python, C++
|
bgneal@4
|
6 :slug: a-c-python-chained-assignment-gotcha
|
bgneal@4
|
7 :author: Brian Neal
|
bgneal@4
|
8
|
bgneal@4
|
9 Late last night I had a marathon debugging session where I discovered I had
|
bgneal@4
|
10 been burned by not fully understanding chaining assignment statements in
|
bgneal@4
|
11 Python. I was porting some C code to Python that had some chained assignment
|
bgneal@4
|
12 expressions. C and C++ programmers are well used to this idiom which has the
|
bgneal@4
|
13 following meaning:
|
bgneal@4
|
14
|
bgneal@4
|
15 .. sourcecode:: c
|
bgneal@4
|
16
|
bgneal@4
|
17 a = b = c = d = e; // C/C++ code
|
bgneal@4
|
18
|
bgneal@4
|
19 // The above is equivalent to this:
|
bgneal@4
|
20
|
bgneal@4
|
21 a = (b = (c = (d = e)));
|
bgneal@4
|
22
|
bgneal@4
|
23 This is because in C, assignments are actually expressions that return a value,
|
bgneal@4
|
24 and they are right-associative.
|
bgneal@4
|
25
|
bgneal@4
|
26 I knew that Python supported this syntax, and I had a vague memory that it was
|
bgneal@4
|
27 not the same semantically as C, but I was in a hurry. After playing a bit in the
|
bgneal@4
|
28 shell I convinced myself this chained assignment was doing what I wanted. My
|
bgneal@4
|
29 Python port kept this syntax and I drove on. A huge mistake!
|
bgneal@4
|
30
|
bgneal@4
|
31 Hours later, of course, I found out the hard way the two are not exactly
|
bgneal@4
|
32 equivalent. For one thing, in Python, assignment is a statement, not an
|
bgneal@4
|
33 expression. There is no 'return value' from an assignment. The Python syntax
|
bgneal@4
|
34 does allow chaining for convenience, but the meaning is subtly different.
|
bgneal@4
|
35
|
bgneal@4
|
36 .. sourcecode:: python
|
bgneal@4
|
37
|
bgneal@4
|
38 a = b = c = d = e # Python code
|
bgneal@4
|
39
|
bgneal@4
|
40 # The above is equivalent to these lines of code:
|
bgneal@4
|
41 a = e
|
bgneal@4
|
42 b = e
|
bgneal@4
|
43 c = e
|
bgneal@4
|
44 d = e
|
bgneal@4
|
45
|
bgneal@4
|
46 Now usually, I suspect, you can mix the C/C++ meaning with Python and not get
|
bgneal@4
|
47 tripped up. But I was porting some tricky red-black tree code, and it made
|
bgneal@4
|
48 a huge difference. Here is the C code first, and then the Python.
|
bgneal@4
|
49
|
bgneal@4
|
50 .. sourcecode:: c
|
bgneal@4
|
51
|
bgneal@4
|
52 p = p->link[last] = tree_rotate(q, dir);
|
bgneal@4
|
53
|
bgneal@4
|
54 // The above is equivalent to:
|
bgneal@4
|
55
|
bgneal@4
|
56 p = (p->link[last] = tree_rotate(q, dir));
|
bgneal@4
|
57
|
bgneal@4
|
58
|
bgneal@4
|
59 The straight (and incorrect) Python port of this code:
|
bgneal@4
|
60
|
bgneal@4
|
61 .. sourcecode:: python
|
bgneal@4
|
62
|
bgneal@4
|
63 p = p.link[last] = tree_rotate(q, d)
|
bgneal@4
|
64
|
bgneal@4
|
65 # The above code is equivalent to this:
|
bgneal@4
|
66 temp = tree_rotate(q, d)
|
bgneal@4
|
67 p = temp # Oops
|
bgneal@4
|
68 p.link[last] = temp # Oops
|
bgneal@4
|
69
|
bgneal@4
|
70 Do you see the problem? It is glaringly obvious to me now. The C and Python
|
bgneal@4
|
71 versions are not equivalent because the Python version is executing the code in
|
bgneal@4
|
72 a different order. The flaw comes about because ``p`` is used multiple times in
|
bgneal@4
|
73 the chained assignment and is now susceptible to an out-of-order problem.
|
bgneal@4
|
74
|
bgneal@4
|
75 In the C version, the tree node pointed at by ``p`` has one of its child links
|
bgneal@4
|
76 changed first, then ``p`` is advanced to the value of the new child. In the
|
bgneal@4
|
77 Python version, the tree node referenced by the name ``p`` is changed first,
|
bgneal@4
|
78 and then its child link is altered! This introduced a very subtle bug that cost
|
bgneal@4
|
79 me a few hours of bleary-eyed debugging.
|
bgneal@4
|
80
|
bgneal@4
|
81 Watch out for this when you are porting C to Python or vice versa. I already
|
bgneal@4
|
82 avoid this syntax in both languages in my own code, but I do admit it is nice
|
bgneal@4
|
83 for conciseness and let it slip in occasionally.
|