Home > front end >  Using 'self' in for loop and conditional statements in python
Using 'self' in for loop and conditional statements in python

Time:10-12

I am learning linkedlists in python. In this website, and in one of the methods,

    def add_last(self, node):
    if self.head is None:
        self.head = node
        return
    for current_node in self:
        pass
    current_node.next = node

What does the line for current_node in self: mean? How could one iterate through self? What does it mean to iterate through self?

Below is the entire code for the implementation of a linkedlist in python

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

    def __repr__(self):
        return self.data

class LinkedList:
    def __init__(self):
        self.head = None
  
    def __init__(self, nodes=None):
        self.head = None
        if nodes is not None:
            node = Node(data=nodes.pop(0))
            self.head = node
        for elem in nodes:
            node.next = Node(data=elem)
            node = node.next
    
    def __repr__(self):
        node = self.head
        nodes = []
        while node is not None:
            nodes.append(node.data)
            node = node.next
        nodes.append("None")
        return " -> ".join(nodes)

CodePudding user response:

In python, we can use several different types/classes in for loops (i.e. lists, strings, etc). The python interpreter will accept any object that is an iterable when building a for loop. For a object to be an iterable, its class must implement the __iter__ method. As long as the class implements this method, python doesn't care if you refer to the object using self or a standard variable.

Here is an example from the wiki page of an iterable object. Note that there is another layer to this with the __next__ method. A class with __next__ defined is called an iterator. The iterator provides the actual logic for producing a sequence of elements for the for loop. An iterable must return an iterator object with __iter__ For more complex types, the iterator may need to be a separate class from the iterable, but in simple cases (like the following example) they can be the same class.

    import random
    
    class RandomIterable:
        def __iter__(self):
            return self
        def __next__(self):
            if random.choice(["go", "go", "stop"]) == "stop":
                raise StopIteration  # signals "the end"
            return 1

https://wiki.python.org/moin/Iterator

CodePudding user response:

In comments you explain that the LinkedList class has this method:

def __iter__(self): 
    node = self.head
    while node is not None:
        yield node
        node = node.next

This makes it possible to iterate an instance of LinkedList with a for..in loop. In fact the following piece of code in add_last:

    for current_node in self:
        pass

Can be written in more verbose manner like this:

    it = iter(self)
    try:
        while True:
            current_node = next(it)
    except:
        pass

So add_node could have been written like this:

def add_last(self, node):
    if self.head is None:
        self.head = node
        return
    it = iter(self)
    try:
        while True:
            current_node = next(it)
    except:
        current_node.next = node

it represents the iterator that the for loop implicitly creates, but with this code it is easier to follow what is happening.

Let's say you have two nodes in your list with values 1 and 2, then the execution sequence of the more verbose add_node is as follows:

add_last __iter__
self.head is not None
it = iter(self)
next(it)
node = self.head
node is not None
yield node (with value 1)
current_node = (node with value 1)
next(it)
node = node.next
node is not None
yield node (with value 2)
current_node = (node with value 2)
next(it)
node = node.next
node is None (!)
raise StopIteration() (implicit)
except:
current_node.next = node

Effectively, the last value of current_node is the last node in the list, and assigning the new node to its next attribute is exactly what you want to happen to extend the list with that node.

Other comments

Not your question, but there are some issues in your code:

  • The second __init__ definition in LinkedList overwrites the first. There is no such concept of overloading in Python. But also you don't need the first definition in this case, as the second version has a default value for its second parameter.

  • The indentation of the for loop in that __init__ method is wrong. It should be inside the if block.

  • Calling .pop(0) is not nice for the caller of the constructor: it mutates the given list, and this might be an undesired side-effect for the caller.

  • The __repr__ method on ListNode assumes that the values received from Node's __repr__ method are strings, as otherwise the join will fail. If ever you want to use nodes for storing integers (for example), this method will fail. To avoid that, cast self.data to string in the Node's __repr__ method.

  • Related