L.P.
L.P.

Reputation: 406

Calling the function 'list(...)' directly on a created class

I am aware that within the Python Class creation one could overload some specific functionalities, so that when some of Python's built-in functions are called on the created object they are able to act properly on them ie.

class Node(object):    
    def __init__(self, contents=None,leftNode=None, rightNode=None):
        super(Node, self).__init__()
        self.__leftNode = leftNode
        self.__rightNode= rightNode
        self.__contents = contents

    # OverLoad the str function. Prints the address of the Node, and it's contents.
    def __str__(self):  
        addr=repr(self).split(' ')[-1][:-1]
        return str('Addr: {0}\tContents: {1}'.format(addr, self.__contents))

This allows pretty printing of the data that is output when print or str(...) is called on the node object. What I would like to know is if there is a way to do the same for list(...) where in, if I were to pass a Linked List object that was implemented, it would be able to return a python list implementation, which would just append all of the nodes contents to a list, and return it.

I know this sounds somewhat simple, and unneeded, but about to produce a Binary Tree Class implementation, and would like to be able to produce a Heap Sort Able list in itself. I tried to implement the iter property, but just went about creating a weird infinite loop.

Edit

Link to full Implementation of the LinkedListData Type: Linked List Gist.

Main difference between itself, and Pythons Linked List is easier appending to whichever side of the list would be wanted, and ability to enable type safety, which can be turned on by indicating the type within the list from the beginning. Added implementation cues supplied by @Leva7. As this was my first "Pure" Data Structure, any tips on best practices, sans DocStrings became familiar with those a bit more, would be appreciated, and welcome, also inefficiencies within the code base as well.

Upvotes: 1

Views: 66

Answers (2)

illright
illright

Reputation: 4043

This is done by implementing the __iter__ method. The list constructor will use it to iterate over your object and create a list from that. Keep in mind that it should be a generator method, rather than returning a list.

Example:

class Test:
    def __iter__(self):
        for i in range(5):
            yield i

ls = list(Test())
print(ls)

The output will be:

[0, 1, 2, 3, 4]

In your case, you would take the first node, and until the end (which is somehow marked in your implementation) you yield the next node, so something like that:

def __iter__(self):
    curr_node = self
    while curr_node.__not_last:
        yield curr_node.__contents
        curr_node = curr_node.__rightNode

And for that you need a __not_last boolean property in the Node object

For future reference, here is the OP's full linked list implementation

Upvotes: 4

tdelaney
tdelaney

Reputation: 77337

You already have a good answer, but its only part of the story so I thought I'd type out the long-winded version. You are interested in python's iterator protocol which is implemented by two methods: __iter__() and __next__(). The protocol is simple: __iter__ returns some object with a __next__ method and repeated calls to __next__ return values until finally StopIteration is raised. Objects with __iter__ are iterable while objects with __next__ are iterators.

Implementors have several choices on how to implement an iterable. In this case, A is its own iterator.

class A:
    """A class that is its own iterator"""
    def __iter__(self):
        # ....
        return self

    def __next__(self):
        # ...
        return next_value

This is a common strategy when you want all iterators of an object to see the same thing. A file object is a good example:

>>> f is f.__iter__()
True

Here, there is only one backing file with one file pointer and all iterators use it. This is why nested for loops work for files, for example. Its also the source of many errors. Its common for people to put a __next__ on an object (because that's what examples tend to show) only to find their code breaks when there are 2 iterators on that object.

with open('foo') as foo:
    next(foo) # advance iterator to get rid of line
    for line in foo:
        if line.startswith('begin token'):
            for line in foo: # second iterator gets same view
                if line.startswith('end token'):
                    break

In this case, B creates a separate object to do the iteration:

class BIter:
    """A iterator for another class"""
    def __init__(self, b_obj):
        self.b_obj = b_obj

    def __next__(self):
        ...
        return next_value

class B:
    """A class that uses another iterator class"""
    def __iter__(self):
        return BIter(self)

A list object is a good example, all iterators of the list are independent:

>>> l is l.__iter__()
False
>>> next(l)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator

Finally, you can let python create the iterator for you with generator expression (that is, a function that yields) or any other function that returns an iterator.

class C:
    """A class that uses a generator to let python build the iterator
    class for it.
    """
    def __iter__(self):
        for x in whatever:
            yield x

Upvotes: 2

Related Questions