Meysam Meysam - 4 months ago 9
Python Question

How to implement "next" for a dictionary object to be iterable?

I've got the following wrapper for a dictionary:

class MyDict:
def __init__(self):
self.container = {}

def __setitem__(self, key, value):
self.container[key] = value

def __getitem__(self, key):
return self.container[key]

def __iter__(self):
return self

def next(self):
pass

dic = MyDict()
dic['a'] = 1
dic['b'] = 2

for key in dic:
print key


My problem is that I don't know how to implement the
next
method to make
MyDict
iterable. Any advice would be appreciated.

Answer

Dictionaries are themselves not an iterator (which can only be iterated over once). You usually make them an iterable, an object for which you can produce multiple iterators instead.

Drop the next method altogether, and have __iter__ return an iterable object each time it is called. That can be as simple as just returning an iterator for self.container:

def __iter__(self):
    return iter(self.container)

If you must make your class an iterator, you'll have to somehow track a current iteration position and raise StopIteration once you reach the 'end'. A naive implementation could be to store the iter(self.container) object on self the first time __iter__ is called:

def __iter__(self):
    if not hasattr(self, '_iter'):
        self._iter = iter(self.container)
    return self

def next(self):
    if not hasattr(self, '_iter'):
        raise ValueError('Not an iterator yet, call __iter__ first')
    return next(self._iter)

at which point the iter(self.container) object takes care of tracking iteration position for you, and will raise StopIteration when the end is reached. It'll also raise an exception if the underlying dictionary was altered (had keys added or deleted) and iteration order has been broken.

Another way to do this would be to just store in integer position and index into list(self.container) each time, and simply ignore the fact that insertion or deletion can alter the iteration order of a dictionary:

_iter_index = 0

def __iter__(self):
    return self

def next(self):
    idx = self._iter_index
    if idx is None or idx >= len(self.container):
        # once we reach the end, all iteration is done, end of.
        self._iter_index = None
        raise StopIteration()
    value = list(self.container)[idx]
    self._iter_index = idx + 1
    return value

In both cases your object is then an iterator that can only be iterated over once. Once you reach the end, you can't restart it again.