[Tutor] recursive generator

Steven D'Aprano steve at pearwood.info
Sun Mar 7 14:27:40 CET 2010


On Sun, 7 Mar 2010 11:58:05 pm spir wrote:
> Hello,
>
> Is it possible at all to have a recursive generator? 


Yes.


>>> def recursive_generator(n):
...     if n == 0:
...         return
...     yield n
...     for i in recursive_generator(n-1):
...         yield i
...
>>> it = recursive_generator(5)
>>> it.next()
5
>>> it.next()
4
>>> it.next()
3
>>> it.next()
2
>>> it.next()
1
>>> it.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration


> I think at a 
> iterator for a recursive data structure (here, a trie). The following
> code does not work: it only yields a single value. Like if
> child.__iter__() were never called.
>
>     def __iter__(self):
>         ''' Iteration on (key,value) pairs. '''
>         print '*',
>         if self.holdsEntry:
>             yield (self.key,self.value)
>         for child in self.children:
>             print "<",
>             child.__iter__()
>             print ">",
>         raise StopIteration


__iter__ should be an ordinary function, not a generator. Something like 
this should work:

# Untested.
    def __iter__(self):
        ''' Iteration on (key,value) pairs. '''
        def inner():
            print '*',  # Side effects bad...
            if self.holdsEntry:
                yield (self.key,self.value)
            for child in self.children:
                print "<",
                child.__iter__()
                print ">",
            raise StopIteration
        return inner()


This means that your class won't *itself* be an iterator, but calling 
iter() on it will return a generator object, which of course is an 
iterator.

If you want to make your class an iterator itself, then you need to 
follow the iterator protocol. __iter__ must return the instance itself, 
and next must return (not yield) the next iteration.

class MyIterator(object):
    def __init__(self, n):
        self.value = n
    def next(self):
        if self.value == 0:
            raise StopIteration
        self.value //= 2
        return self.value
    def __iter__(self):
        return self

See the discussion in the docs about the iterator protocol:

http://docs.python.org/library/stdtypes.html#iterator-types



> Why is no child.__iter__() executed at all? I imagine this can be
> caused by the special feature of a generator recording current
> execution point.

That's exactly what generators do: when they reach a yield, execution is 
halted, but the state of the generator is remembered. Then when you 
call the next() method, execution resumes.



> (But then, is it at all possible to have a call in a 
> generator? Or does the issue only appear whan the callee is a 
> generator itself?) Else, the must be an obvious error in my code.


I don't understand what you mean.


-- 
Steven D'Aprano


More information about the Tutor mailing list