-1

This seems like a bug to me where I couldn't really figure it out why.

I have a very simple self-defined LinkedList class:

class MyLinkedList:

    def __init__(self):
        self.head = None

    def __iter__(self):
        self.currNode = self.head
        return self

    def __next__(self):
        if self.currNode:
            res = self.currNode
            self.currNode = self.currNode.next
            return res
        else:
            raise StopIteration

    def addAtHead(self, val: int) -> None:
        currNode = self.head

        self.head = Node(val, currNode)

The problem comes inside iter method. After I added a node at head to a empty linkedList, the iter still think the self.head is None! In debug node, I can see self.head is a valid not none instance, but after assigning its value to self.currNode, self.currNode turns out to be None.

I thought it can be a problem of property issue so I changed the attribute to a property. But the problem still exists. Where am I wrong?

    @property
    def head(self):
        return self._head

    @head.setter
    def head(self, x):
        self._head = x

enter image description here

Adding my calling stack:

    ll = MyLinkedList()
    ll.addAtTail(6)
    ll.addAtHead(5)
    ll[0] # is None 

    #where a __getitem__ is defined as:
    def __getitem__(self, i):
        n = 0
        for node in self:
            if n == i:
                return node
            n += 1
        raise IndexError


class Node:
    def __init__(self, val=None, next=None):
        self.val = val
        self.next = next         
jtcloud
  • 521
  • 1
  • 4
  • 18
  • 2
    Don't make a list its own iterator. Some tutorials suggest doing that, but it's a really bad idea, since it breaks if you try to use nested loops or iterate over the list concurrently or reentrantly. – user2357112 May 15 '20 at 21:25
  • https://stackoverflow.com/questions/4019971/how-to-implement-iter-self-for-a-container-object-python – azro May 15 '20 at 21:25
  • 1
    "the iter still think the self.head is None!" I don't understand; the screenshot you show of the debugger clearly shows `head = {Node} ...`. At any rate, can you show an example of *trying to use the class* that causes an unexpected error? IDEs don't always get everything right, or they may show things out of sync. – Karl Knechtel May 15 '20 at 21:27
  • yes, head is a **Node**, which is not **none**, that they are two words @KarlKnechtel – jtcloud May 15 '20 at 21:28
  • 2
    You need to show how you're using this (with [MCVE]) for us to provide a complete answer, but the main issue is likely that you haven't written an iterator class. Iterators must have idempotent `__iter__` methods (in 99.99% of cases they should do *nothing* but `return self`), as Python assumes it can blithely call `__iter__` on an existing iterator and change nothing. If you want this to be an *iterable*, `__next__` should not be defined, and, usually, you write `__iter__` as a generator function (migrating all the logic into `__iter__` and using `yield` where `__next__` would use `return`). – ShadowRanger May 15 '20 at 21:29
  • @ShadowRanger, added my call stack at bottom. – jtcloud May 15 '20 at 21:33
  • @azro, I guess my question is more about how to set self.variable inside __iter__ method and the attribute is a property which is dynamically updated over time. – jtcloud May 15 '20 at 21:35
  • @T.Yun you set it the way you always do. But again, **your list should not be an iterator to begin with**. This is quite important to understand. – juanpa.arrivillaga May 15 '20 at 21:36
  • As an aside, why are you making `head` a `property`??? Your getter and setter does nothing but get and set the attribute... it shouldn't be a property but a regular attribute. That defeats the entire purpose of `property`, which is to give you encapsulation without boilerplate getters and setters. – juanpa.arrivillaga May 15 '20 at 21:37
  • @juanpa.arrivillaga, as I said, I didn't use property at beginning but I suspect it's because of that. It didn't work out. But without it, it doesn't work either. It is my experiment, so please not suggest "opinioned" way to say what should what should not. If there are exceptions, I can try to handle it. But that's not what I'm trying to ask here. – jtcloud May 15 '20 at 21:39
  • lookt at "self.currNode = self.currNode.next" it should be self.currNode = self.currNode.next() where is the parenthesis at the end? – Narcisse Doudieu Siewe May 15 '20 at 21:40
  • @user1438644, it is from a self-defined class Node, where next is an attribute of Node. – jtcloud May 15 '20 at 21:40
  • could you share "Node"? difficult to help without it – Narcisse Doudieu Siewe May 15 '20 at 21:43
  • @user1438644, added at bottom. Appreciate – jtcloud May 15 '20 at 21:44
  • ok look! on "Node", "next" should be a function not a variable! when you iter, for call object.next() not object.next – Narcisse Doudieu Siewe May 15 '20 at 21:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213971/discussion-between-t-yun-and-user1438644). – jtcloud May 15 '20 at 21:47
  • 1
    @user1438644: That's a different thing. The node isn't supposed to be an iterator. – user2357112 May 15 '20 at 21:55
  • having a method "next" doesn"t signify that "Node" is an iterator! you need "__iter__" and "__next__" for this! I'm talking about "next" method! "next" is just an artifact to help get the next value only – Narcisse Doudieu Siewe May 15 '20 at 22:11
  • @user1438644: I have no idea why you think `next` needs to be a method, then. – user2357112 May 15 '20 at 22:17
  • 1
    I can't reproduce your issue, though maybe I'm doing something different than you are, since it's a little hard to tell what exactly you're running. I used the `MyLinkedList` class from your first block with the addition of the `__getitem__` method in your last block. I also created the `Node` class from your last block. Then the rest of the code in the last block worked fine. I get a node when I do `ll[0]`. – Blckknght May 15 '20 at 22:54

1 Answers1

0
class Node:
    def __init__(self, val=None, nxt=None):
        self.val = val
        self.n = nxt 

class MyLinkedList:

    def __init__(self):
        self.head = None

    def __iter__(self):
        self.currNode = self.head
        return self

    def __next__(self):
        if self.currNode:
            res = self.currNode.val
            self.currNode = self.currNode.n
            return res
        else:
            raise StopIteration

    def addAtHead(self, val: int) -> None:
        currNode = self.head

        self.head = Node(val, currNode)


n = MyLinkedList()
n.addAtHead(4)
n.addAtHead(5)
n.addAtHead(7)

for i in n:
    print(i)

got

7
5
4
azro
  • 53,056
  • 7
  • 34
  • 70
  • 1
    Is this why you thought `next` needed to be a method? You could just access the value through `val`. You don't need a method for that. `next` was intended to be the link to the next node in the original code. It wasn't supposed to be a value accessor. Naming a node value accessor `next` is a weird name choice. – user2357112 May 15 '20 at 22:20
  • An answer without a word values nothing, you may explain what the problem was, and what you did to solve it, and why does it solves it – azro May 16 '20 at 07:04