Reputation: 406
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.
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
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
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 yield
s) 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