I was trying Python comma assignment to parallelly change the values of variables. I believe Python will evaluate the values of the expressions on the right-hand side first and then, assign those values to the variables on the left-hand side. One specific example is in this reverse linked list code:
# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def reverseList(self, head: ListNode) -> ListNode: prev = None curr = head while curr != None: prev, curr.next, curr = curr, prev, curr.next return prev
prev, curr.next, curr = curr, prev, curr.next
works perfectly fine. If I change the order to prev, curr, curr.next = curr, curr.next, prev
, I expect my code works the same:
# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def reverseList(self, head: ListNode) -> ListNode: prev = None curr = head while curr != None: prev, curr, curr.next = curr, curr.next, prev return prev
However, I got an error instead in the second code for that line:
AttributeError: 'NoneType' object has no attribute 'next' prev, curr, curr.next = curr, curr.next, prev
which doesn’t make sense to me because Python evaluates the right-hand side expressions first before assigning, which means the order of where I put the expressions shouldn’t matter. Am I missing something here?
Advertisement
Answer
You are correct that Python evaluates the right-side first; however, what’s missing is that Python will evaluate each side in order. We can see what this means by breaking the two options down into individual assignment statements (Full example on Hastebin):
# First example: def reverseList(head: ListNode) -> ListNode: prev = None curr = head while curr != None: # prev, curr.next, curr = curr, prev, curr.next x = curr y = prev z = curr.next prev = x curr.next = y curr = z return prev
# Second example: def reverseList2(head: ListNode) -> ListNode: prev = None curr = head while curr != None: # prev, curr, curr.next = curr, curr.next, prev x = curr y = curr.next z = prev prev = x curr = y curr.next = z return prev
The important part here is that in the first example, we assign the value of curr.next
before we assign the value of curr
, whereas in the second example we assign the value of curr.next
after. What this means is that when we go to assign curr.next
, we’ve already updated curr
, so when the interpreter accesses curr.next
again for another assignment it will pull the wrong reference back. The error occurs when you’ve set curr to None
, and then after doing so, you then try to access curr.next
to complete your reassignment operation.
A side note, try to refrain from using next
as a variable name. As it’s a builtin function, it can cause some confusion, though it’s probably fine.