[Tutor] recursive generator

2010-03-07 Thread spir
Hello,

Is it possible at all to have a recursive generator? 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

With the debug prints in code above, for e in t: print e outputs:

* ('', 0)
   

print len(t.children) # -- 9

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. (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.


Denis
-- 


la vita e estrany

spir.wikidot.com

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Steven D'Aprano
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
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Stefan Behnel

Steven D'Aprano, 07.03.2010 14:27:

On Sun, 7 Mar 2010 11:58:05 pm spir wrote:

 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()


That's just an unnecessarily redundant variation on the above. It's 
perfectly ok if __iter__() is a generator method.


Stefan

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


[Tutor] recursive generator

2010-03-07 Thread Hugo Arts
sorry, forgot to forward this to the list.

On Sun, Mar 7, 2010 at 1:58 PM, spir denis.s...@gmail.com wrote:
 Hello,

 Is it possible at all to have a recursive generator? 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

 With the debug prints in code above, for e in t: print e outputs:

 * ('', 0)


 print len(t.children) # -- 9

 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. (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.


 Denis

remember that child.__iter__ returns a generator object. Merely
calling the function won't do anything. To actually get elements from
a generator object, you need to call next() on the returned iterator,
or iterate over it in another way (e.g. a for loop). I would write
this method more or less like so:

from itertools import chain

def __iter__(self):
   this_entry = [(self.key, self.value)] is self.holds_entry else []
   return chain(this_entry, *[iter(c) for c in self.children])

(Warning! untested! I'm pretty sure chain will work like this, but you
might have to do a little tweaking)
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread Rich Lovely
On 7 March 2010 12:58, spir denis.s...@gmail.com wrote:
 Hello,

 Is it possible at all to have a recursive generator? 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

 With the debug prints in code above, for e in t: print e outputs:

 * ('', 0)


 print len(t.children) # -- 9

 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. (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.


 Denis
 --
 

 la vita e estrany

 spir.wikidot.com

 ___
 Tutor maillist  -  tu...@python.org
 To unsubscribe or change subscription options:
 http://mail.python.org/mailman/listinfo/tutor


You are calling child.__iter__(), but it's return value is being thrown away.

What you want to be doing is something like

def __iter__(self):
if self.holdsEntry:
yield self.entry
for child in self.children:
print 
for val in child: #implicit call to child.__iter__()
yield val
print 

Then, when the child.__iter__() is called, the returned iterator is
iterated, and the values are passed up the call stack.  There's
probably a more terse way of doing this using itertools, but I think
this is probably more readable.

Hope this clears things up (a little, anyway...)
-- 
Rich Roadie Rich Lovely

Just because you CAN do something, doesn't necessarily mean you SHOULD.
In fact, more often than not, you probably SHOULDN'T.  Especially if I
suggested it.

10 re-discover BASIC
20 ???
30 PRINT Profit
40 GOTO 10
___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor


Re: [Tutor] recursive generator

2010-03-07 Thread spir
On Sun, 7 Mar 2010 17:20:07 +0100
Hugo Arts hugo.yo...@gmail.com wrote:

 On Sun, Mar 7, 2010 at 1:58 PM, spir denis.s...@gmail.com wrote:
  Hello,
 
  Is it possible at all to have a recursive generator? 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
 
  With the debug prints in code above, for e in t: print e outputs:
 
  * ('', 0)
 
 
  print len(t.children) # -- 9
 
  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. (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.
 
 
  Denis
 
 remember that child.__iter__ returns a generator object. Merely
 calling the function won't do anything. To actually get elements from
 a generator object, you need to call next() on the returned iterator,
 or iterate over it in another way (e.g. a for loop). I would write
 this method more or less like so:
 
 from itertools import chain
 
 def __iter__(self):
 this_entry = [(self.key, self.value)] is self.holds_entry else []
 return chain(this_entry, *[iter(c) for c in self.children])
 
 (Warning! untested! I'm pretty sure chain will work like this, but you
 might have to do a little tweaking)

@ Hugo  Steven
Thank you very much. I'll study your proposals as soon as I can, then tell you 
about the results.
In the meanwhile, I (recursively) constructed the collection of entries and 
returned iter(collection). [This works, indeed, but I also want do know how to 
properly build a recursive iterator.]

Denis
-- 


la vita e estrany

spir.wikidot.com

___
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor