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.