473,386 Members | 1,860 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,386 software developers and data experts.

Recursive Generator Question

I've been playing around with generators and have run into a
difficulty. Suppose I've defined a Node class like so:

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self): return self

def next(self):
""" Returns iteration over terminal nodes of this tree. """
if self.data:
yield self
else:
for child in self.children:
for terminal in child:
yield terminal
And then suppose I create a little binary tree like so:

a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
ab = Node(left=a, right=b)
cd = Node(left=c, right=d)
abcd = Node(left=ab, right=cd)

for termNodes in abcd:
print termNodes.data # gives an error

when I do this, I get the following error:
Traceback (most recent call last):
File "C:\Documents and Settings\Paul
Chiusano\workspace\sandbox\hello.py", line 69, in ?
print termNodes.data
AttributeError: 'generator' object has no attribute 'data'

For some reason, that iteration is returning generators instead of
leaves. Curiously, if I replace that for loop with

for termNodes in terminals(abcd): # see definition below
print termNodes.data # no problem!

then it actually prints out:
a
b
c
d

Here's the definition of terminals--pretty much identical to next.

def terminals(t):
""" Returns an iteration over the leaves of a tree, t """
if t.data: # if I have data, then I'm a terminal node
yield t
else:
for child in t.children:
for terminal in terminals(child):
yield terminal

Am I missing something? Or is it not possible to define recursive
generators in this way?

Thanks,
Paul
Jul 18 '05 #1
8 2280
Paul Chiusano wrote:
I've been playing around with generators and have run into a
difficulty. Suppose I've defined a Node class like so:

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self): return self

def next(self):
""" Returns iteration over terminal nodes of this tree. """
if self.data:
yield self
else:
for child in self.children:
for terminal in child:
yield terminal
And then suppose I create a little binary tree like so:

a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
ab = Node(left=a, right=b)
cd = Node(left=c, right=d)
abcd = Node(left=ab, right=cd)

for termNodes in abcd:
print termNodes.data # gives an error

when I do this, I get the following error:
Traceback (most recent call last):
File "C:\Documents and Settings\Paul
Chiusano\workspace\sandbox\hello.py", line 69, in ?
print termNodes.data
AttributeError: 'generator' object has no attribute 'data'

For some reason, that iteration is returning generators instead of
leaves. Curiously, if I replace that for loop with

for termNodes in terminals(abcd): # see definition below
print termNodes.data # no problem!

then it actually prints out:
a
b
c
d

Here's the definition of terminals--pretty much identical to next.

def terminals(t):
""" Returns an iteration over the leaves of a tree, t """
if t.data: # if I have data, then I'm a terminal node
yield t
else:
for child in t.children:
for terminal in terminals(child):
yield terminal

Am I missing something? Or is it not possible to define recursive
generators in this way?
The generators are a red herring :) Iterating over an object calls
__iter__ on the object, then repeatedly calls next on the object
returned by __iter__. Each object next returns is taken as a value for
iteration.

So, if we examine your node class, we see that when __iter__ is
called, the same object is returned. When next is called on the node
class, a generator is returned. Oops. Throw away your current __iter__
method and rename your next method __iter__. Now when __iter__ is
called, it will return a generator, and when next is called on the
generator, you will get the leaf nodes you were looking for.

Jp


Thanks,
Paul


Jul 18 '05 #2
Why do you define __iter__? And Node.next is your generator function, so
you have to invoke it, it's not invoked implicitly. You are not creating an
iterator yourself, the generator function returns one for you. That
iterator has a next( ) method and that's what's getting called to iterate
through the tree. So there is no need to actually name the generator
function in your class "next", you can name it anything.

BTW, here is the definition for generator functions:
http://docs.python.org/ref/types.html#l2h-90

Here is the way I think your code should be with minimal changes (I put in
comments for the changes):
class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

# Got rid of the __iter__ method

def next(self):
"""Generator function""" # Better description
if self.data:
yield self
else:
for child in self.children:
for terminal in child.next(): # Changed child to
child.next()
yield terminal

if '__main__'==__name__:
a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
ab = Node(left=a, right=b)
cd = Node(left=c, right=d)
abcd = Node(left=ab, right=cd)
for termNodes in abcd.next(): # Changed abcd to
abcd.next()
print termNodes.data # no error

Dan

"Paul Chiusano" <pc******@umich.edu> wrote in message
news:65**************************@posting.google.c om...
I've been playing around with generators and have run into a
difficulty. Suppose I've defined a Node class like so:

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self): return self

def next(self):
""" Returns iteration over terminal nodes of this tree. """
if self.data:
yield self
else:
for child in self.children:
for terminal in child:
yield terminal
And then suppose I create a little binary tree like so:

a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
ab = Node(left=a, right=b)
cd = Node(left=c, right=d)
abcd = Node(left=ab, right=cd)

for termNodes in abcd:
print termNodes.data # gives an error

when I do this, I get the following error:
Traceback (most recent call last):
File "C:\Documents and Settings\Paul
Chiusano\workspace\sandbox\hello.py", line 69, in ?
print termNodes.data
AttributeError: 'generator' object has no attribute 'data'

For some reason, that iteration is returning generators instead of
leaves. Curiously, if I replace that for loop with

for termNodes in terminals(abcd): # see definition below
print termNodes.data # no problem!

then it actually prints out:
a
b
c
d

Here's the definition of terminals--pretty much identical to next.

def terminals(t):
""" Returns an iteration over the leaves of a tree, t """
if t.data: # if I have data, then I'm a terminal node
yield t
else:
for child in t.children:
for terminal in terminals(child):
yield terminal

Am I missing something? Or is it not possible to define recursive
generators in this way?

Thanks,
Paul

Jul 18 '05 #3
pc******@umich.edu (Paul Chiusano) writes:
I've been playing around with generators and have run into a
difficulty. Suppose I've defined a Node class like so:

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self): return self

def next(self):
""" Returns iteration over terminal nodes of this tree. """
if self.data:
yield self
else:
for child in self.children:
for terminal in child:
yield terminal


If you are defining next yourself, you are defining an iterator, not a
generator, and you should use return statements, not yield statements.
But then you also need to keep track of where you are in the tree.
If you like to use a generator to save the state for you, one way
that works is the following.

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self):
if self.data != None:
yield self.data
for child in self.children:
if child != None:
for terminal in child:
yield terminal

a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
ab = Node(left=a, right=b)
cd = Node(left=c, right=d)
abcd = Node(left=ab, right=cd)

for termNodes in abcd:
print termNodes

# Produces:
#
# a
# b
# c
# d

Notes:

- __iter__ is a generator, so when called it returns an iterator
- No explicit next method
- termNodes.data changed to termNodes
- child != None checked

To the experts: I don't recall seeing __iter__ be a generator like
this before. How would you code it?

Dan
Jul 18 '05 #4
[Dan Christensen]
....
def __iter__(self):
if self.data != None:
yield self.data
for child in self.children:
if child != None:
for terminal in child:
yield terminal
....
To the experts: I don't recall seeing __iter__ be a generator like
this before. How would you code it?


The way you did. A generator *is* a (one kind of) iterator, so it's
fine to use a generator anywhere an iterator is desired.
(Technically, the __iter__ method there is called a
generator-function, and the thing it returns is called a
generator-iterator.)
Jul 18 '05 #5
"Jp Calderone" <ex*****@divmod.com> wrote in message
news:ma**************************************@pyth on.org...
Paul Chiusano wrote: ......... The generators are a red herring :) Iterating over an object calls
__iter__ on the object, then repeatedly calls next on the object
returned by __iter__. Each object next returns is taken as a value for
iteration.

So, if we examine your node class, we see that when __iter__ is
called, the same object is returned. When next is called on the node
class, a generator is returned. Oops. Throw away your current __iter__
method and rename your next method __iter__. Now when __iter__ is
called, it will return a generator, and when next is called on the
generator, you will get the leaf nodes you were looking for.


I think a clarification is in order here. Done this way, __iter__ *IS* a
generator function and it returns an iterator, not a generator. The next( )
is called on the iterator returned by __iter__. Using __iter__ as the
generator takes advantage of the fact that the 'for' statement invokes it
implicitly. But the generator could have any name and it would just have to
be called explicitly.

I would rather do it the explicit way, and it's not just because I'm
following the advice I've been getting from a lot of people in another
thread, where I was on the 'implicit' side of the argument. ;-)

But this way, the 'implicit' call to __iter__, works too.

Dan
Jul 18 '05 #6
Paul Chiusano wrote:
I've been playing around with generators and have run into a
difficulty. Suppose I've defined a Node class like so:

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self): return self

def next(self):
""" Returns iteration over terminal nodes of this tree. """
if self.data:
yield self
else:
for child in self.children:
for terminal in child:
yield terminal
And then suppose I create a little binary tree like so:

a = Node('a')
b = Node('b')
c = Node('c')
d = Node('d')
ab = Node(left=a, right=b)
cd = Node(left=c, right=d)
abcd = Node(left=ab, right=cd)

for termNodes in abcd:
print termNodes.data # gives an error

when I do this, I get the following error:
Traceback (most recent call last):
File "C:\Documents and Settings\Paul
Chiusano\workspace\sandbox\hello.py", line 69, in ?
print termNodes.data
AttributeError: 'generator' object has no attribute 'data'

For some reason, that iteration is returning generators instead of
leaves. <snip> Am I missing something? Or is it not possible to define recursive
generators in this way?


It is possible to define recursive generators. To fix the code, copy the
contents of next() into __iter__().

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self):
if self.data:
yield self
else:
for child in self.children:
for terminal in child:
yield terminal

Iterators and generators can get a little confusing. Note that:

1. Calling __iter__() should return an object with next() on it.
2. When you call a function containing yield, it returns a generator.
3. A generator is function with next() on it.

Conclusion, __iter__() should have yield in it.

To expand point 2 - calling a function containing yield does *not*
return the yielded results. It returns an generator object and repeated
calls on next() of the generator object return the yielded results.

When you put it all together, Python does try to make this easier to
use. You don't need to always go through the extra step of implementing
next(), you just have to put yield in the __iter__.

Hopefully this makes some sense. Play around with generators to get a
better idea.

--
Shalabh

Jul 18 '05 #7
Shalabh Chaturvedi wrote:
1. Calling __iter__() should return an object with next() on it.
2. When you call a function containing yield, it returns a generator.
3. A generator is function with next() on it.


3 should be
3. A generator is an object with next() on it.

--
Shalabh

Jul 18 '05 #8
pc******@umich.edu (Paul Chiusano) wrote in message news:<65**************************@posting.google. com>...
I've been playing around with generators and have run into a
difficulty. Suppose I've defined a Node class like so:

class Node:
def __init__(self, data=None, left=None, right=None):
self.children = []
self.children.append(left)
self.children.append(right)
self.data = data

def __iter__(self): return self

def next(self):
""" Returns iteration over terminal nodes of this tree. """
if self.data:
yield self
else:
for child in self.children:
for terminal in child:
yield terminal

[ ... ]
For some reason, that iteration is returning generators instead of
leaves. Curiously, if I replace that for loop with


This is because you told it to. The next() method needs to return the
single next element, but the one above is returning a generator, so
you end up with a sequence of generators. If you make the object its
own iterator (returning self in __iter__), you will have to save some
state to remember what position you are at (which is bad since it
prevents multiple iterators)

What you probably meant to do is to put the code in the next() method
directly in __iter__ - that way the generator returned is your
iterator, rather than the object itself.

Brian.
Jul 18 '05 #9

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

10
by: MetalOne | last post by:
I am trying to write a generator function that yields the index position of each set bit in a mask. e.g. for x in bitIndexGenerator(0x16): #10110 print x --> 1 2 4 This is what I have, but...
19
by: Carlos Ribeiro | last post by:
Hello all, Here I am using some deeply nested, tree-like data structures. In some situations I need to traverse the tree; the old-style way to do it is to write a recursive method on the node...
10
by: Steve Goldman | last post by:
Hi, I am trying to come up with a way to develop all n-length permutations of a given list of values. The short function below seems to work, but I can't help thinking there's a better way. ...
7
by: aurora | last post by:
I love generator and I use it a lot. Lately I've been writing some recursive generator to traverse tree structures. After taking closer look I have some concern on its performance. Let's take...
2
by: James Stroud | last post by:
This was my answer to the thread "new in programing": def do_something(*args): print args def do_deeply(first, depth, lim, doit=True, *args): if depth < lim: do_deeply(first+1, depth+1, lim,...
14
by: Fabian Steiner | last post by:
Hello! I have got a Python "Device" Object which has got a attribute (list) called children which my contain several other "Device" objects. I implemented it this way in order to achieve a kind...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However,...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.