emmeowzing
emmeowzing

Reputation: 2161

__next__ and next with arguments

I'm writing an implementation of doubly linked lists. In order to traverse the list, I'm using something like:

class Node:
    """ A node in our linked list """
    def __init__(self, value: Any, next: Union['Node', None] =None,
            previous: Union['Node', None] =None) -> None:
        self.value = value
        self.next = next
        self.previous = previous

    ...

    def __next__(self, direction: int =1) -> Union['Node', None]:
        if direction == 1:
            return self.get_next()
        else:
            return self.get_previous()

    ...

where get_next and get_previous are just getters of self.next and self.previous.

However, PyCharm yells at me for trying to call next as next(some_node, direction=-1). What's the proper way to do this?

Upvotes: 2

Views: 2887

Answers (3)

Copperfield
Copperfield

Reputation: 8510

__next__ is part of the iterator protocol and should be used as described in said protocol, doing otherwise only make problems with the rest python.

In your case just rename the function to simple next and use as some_node.next(-1), though I would change the direction argument to a boolean, as that is how you use it, and its name too. Like this for example

class None: 
    ...

    def next(self, forward:bool=True) -> Union['Node', None]:
        if forward:
            return self.get_next()
        else:
            return self.get_previous()

and use as some_node.next(), some_node.next(False) or even some_node.next(0) (using 0 instead of False for the same effect)

Upvotes: 2

MSeifert
MSeifert

Reputation: 152587

Besides __iter__ there is also __reversed__. Both are required to return iterators. The __next__ method should be implemented on iterators (not on node-classes). Note that all magic methods (when called by a function like next instead of directly invoked) need to implement the expected arguments not more - not less.

For example a doubly linked list could just implement __iter__ and __reversed__ and rely on next and previous attribute of the Node:

class Node(object):
    def __init__(self, val, nxt, prv):
        self.val = val
        self.nxt = nxt
        self.prv = prv


class DoublyLinkedList(object):
    def __init__(self, base=None, last=None):
        self.base = base
        self.last = last

    def prepend(self, val):
        new = Node(val, self.base, None)
        if self.base is None:
            self.base = new
            self.last = new
        else:
            self.base.prv = new
            self.base = new

    def append(self, val):
        new = Node(val, None, self.last)
        if self.last is None:
            self.base = new
            self.last = new
        else:
            self.last.nxt = new
            self.last = new

    def __iter__(self):
        current = self.base
        while current is not None:
            yield current
            current = current.nxt

    def __reversed__(self):
        current = self.last
        while current is not None:
            yield current
            current = current.prv

For example:

dl = DoublyLinkedList()
dl.prepend(10)
dl.prepend(20)
dl.prepend(30)

for i in dl:
    print(i.val)

gives:

30
20
10

similar for reversed:

for i in reversed(dl):
    print(i.val)

# prints:
10
20
30

Upvotes: 4

Josh Lee
Josh Lee

Reputation: 177530

The extra argument to next is a default value, and __next__ doesn't take any extra arguments. Python doesn't have any sort of two-way iterators. If your interface is not exactly the same as for i in obj:, then you should write your own.

Upvotes: 1

Related Questions