Mercurial > public > pelican-blog
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. |