3

I'm writing a linked list for an educational Python library. Here are the important snippets of code:

class Element(object):
  def __init__(self, value, next):
    self.value = value
    self.next = next

class LinkedList(object):
  def __init__(self):
    self.head = None
    self.tail = None

  def insert_back(self, element):
    if self.empty():
      self.insert_front(element)
    else:
      self.tail.next = Element(element, None)
      self.tail = self.tail.next
      # I'd like to replace the above two lines with this
      # self.tail = self.tail.next = Element(element, None)

My issue comes from the last line. According to the top answer to this question, Python's unique implementation of chained assignment is the culprit.

In other languages, the last line would have the same effect as the two lines above it, but Python evaluates the expression Element(element, None) first and then assigns the result from left-to-right, so self.tail is assigned before self.tail.next. This results in the previous tail element not referencing the new tail element, and the new tail element referencing itself.

My question is: is there a way to perform these two assignments with a single statement?

I'm perfectly content using the more explicit two-line assignment; this is just for curiosity's sake.

Community
  • 1
  • 1
skirkpatrick
  • 218
  • 1
  • 8
  • >>> a = b = 1 >>> a 1 >>> b 1 – David Apr 16 '13 at 19:22
  • Comments don't like newlines apparently but in your case self.tail = self.tail.next = Element(element, None) would go right to left – David Apr 16 '13 at 19:24
  • Are you saying `x=1`, `y=2`, `x=y=3` results in `x=2`, `y=3` in python? Because that's not the case. It behaves pretty much like C. – Elmar Peise Apr 16 '13 at 19:24
  • The issue is because I'm modifying the same reference. In most situations, you're both right, it behaves like C. This issue only shows up because I'm assigning an attribute of a reference and reassigning the reference itself. – skirkpatrick Apr 16 '13 at 19:28

1 Answers1

5

Assignment is never chained.

Assignment first evaluates the right-hand expression, then assigns the result to the left-hand targets, one by one, from left to right.

See the assigment statement 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.

So your code:

self.tail = self.tail.next = Element(element, None)

effectively means:

result = Element(element, None)
self.tail = result
self.tail.next = result

You could use this instead, simply reversing the assignment order:

self.tail.next = self.tail = Element(element, None)

which assigns in the correct order:

result = Element(element, None)
self.tail.next = result
self.tail = result

This results in the correct behaviour for a linked list:

>>> head = tail = Element(0, None)
>>> tail.next = tail = Element(1, None)
>>> head.value
0
>>> head.next
<__main__.Element object at 0x10262e510>
>>> head.next.value
1
>>> tail is head.next
True
>>> tail.next = tail = Element(2, None)
>>> tail is head.next.next
True
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • That's what I gathered from the link in the OP. I'm wondering if there is a way (not necessarily with chained assignments) to do it in one line. I don't believe the multiple assignment solution would work, either, as self.tail would end up as None (self.tail is the tail element, so self.tail.next will _always_ be None). – skirkpatrick Apr 16 '13 at 19:35
  • 1
    @skirkpatrick: actually, just reversing the assignments is enough. – Martijn Pieters Apr 16 '13 at 19:42
  • Wow, I totally thought of this, but my brain said "that won't work!" Thank you for having such a thorough, well explained answer! – skirkpatrick Apr 16 '13 at 20:04