2

I am writing simple code to reverse a linked list and realised the assignments can be done on one line, which I found pretty cool:

    def Reverse(head):
      prev_node = None
      curr_node = head

      while curr_node:
        prev_node, curr_node.next, curr_node = curr_node, prev_node, curr_node.next

      return prev_node

But I noticed that the code fails if I reverse the order of assignments between curr_node.next on the LHS (corresponding with prev_node on the RHS) and curr_node on the LHS (...curr_node.next on the RHS)

    def Reverse(head):
      prev_node = None
      curr_node = head
      print(curr_node.data)
      print(curr_node.next.data)
      print(prev_node)

      while curr_node:
        prev_node, curr_node, curr_node.next = curr_node, curr_node.next, prev_node

      return prev_node

The input is

1 2 3 4

The output is

1
2
None

But the following error is produced from the while loop (only on the second block of code; the first one runs fine)

      prev_node, curr_node, curr_node.next = curr_node, curr_node.next, prev_node
    AttributeError: 'NoneType' object has no attribute 'next'

The closest discussion I could find on the subject was here. Which says that the RHS is evaluated first, from left to right. Which I think means that curr_node is stored, then prev_node, then curr_node.next. Then they are assigned to prev_node, curr_node.next and curr_node, respectively. I don't see the difference between the first and second example. Am I missing something simple?

Does anyone know why the first example runs while the second one produces an error?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343

1 Answers1

3

Yes, tuple assignments have the right-hand-side evaluated first (from left to right), and the assignments are executed, also from left to right.

From the Assignment statements documentation:

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 your second case, you assigned None to curr_node, so the next assignment to curr_node.next fails.

In other words, this works:

prev_node, curr_node.next, curr_node = curr_node, None, None

(provided curr_node is still a Node instance with a next attribute when curr_node.next = None is executed), but if you swap the arguments:

prev_node, curr_node, curr_node.next = curr_node, None, None

and now curr_node = None is executed before curr_node.next = prev_node.

Here is a simplified demo to show you the order of the assignment matters:

>>> class Node:
...     next = None
...
>>> curr_node = Node()
>>> curr_node.next, curr_node = None, None  # first attribute, then name
>>> curr_node = Node()
>>> curr_node, curr_node.next = None, None  # first the name, no attribute anymore
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'next'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Ordinarily you'd want one operation to happen before the other, but with the single line assignment the tuple is constructed from the existing values in memory and then the assignments are made. Please correct me if I'm mistaken. – cs95 Jun 16 '17 at 10:30
  • 1
    @Coldspeed: that's almost how it happens. For 2 or 3 value tuples the values are pushed on the stack directly without constructing a tuple first. See [How does swapping of members in the python tuples (a,b)=(b,a) work internally?](//stackoverflow.com/a/21047622) – Martijn Pieters Jun 16 '17 at 10:31
  • Wow guys, at the risk of embarrassing myself, I'll just say that this was my first time asking a question on this site - finally made an account - and I am not disappointed!! @MartijnPieters So thankful for the detail in your answer, as well as the follow up discussion. I'll do my best to pay it forward and answer questions that (I think that) I'm qualified to answer. – Evan Parrish Jun 18 '17 at 06:12