comparison content/Coding/021-python-chained-assignment.rst @ 4:7ce6393e6d30

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