annotate content/Coding/021-python-chained-assignment.rst @ 21:21a29f2520ca

Add related_name to UserProfile model example.
author Brian Neal <bgneal@gmail.com>
date Sat, 27 Aug 2016 13:18:54 -0500
parents 7ce6393e6d30
children
rev   line source
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.