pythonpython-3.xlinked-listvariable-assignmentiterable-unpacking

Order of operations for three (or more) distinct variable assignments using a linked list in python 3


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?


Solution

  • 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'