__iter__ yield

D

duccio

Hello!
Someone knows if it's possible to make this __iter__ function with just
one 'yield' intead of two?
Is there some simpler way to make this __iter__ iter through all nodes?
Thanks!

class Node:
def __init__(self, data=None):
self.childs=[]
self.data=data
def appendNode(self, n):
node=Node(n)
self.childs.append(node)
return node
def __str__(self):
return '<'+str(self.data)+'>'
def __iter__(self):
yield self #1
for n in self.childs:
for nn in n.__iter__():
yield nn #2

n=Node()
n.appendNode(1).appendNode(2).appendNode(3).appendNode(4)
n.appendNode(11).appendNode(22).appendNode(33).appendNode(44)
for node in n:
print node
 
D

Diez B. Roggisch

duccio said:
Hello!
Someone knows if it's possible to make this __iter__ function with just
one 'yield' intead of two?
Is there some simpler way to make this __iter__ iter through all nodes?
Thanks!

class Node:
def __init__(self, data=None):
self.childs=[]
self.data=data
def appendNode(self, n):
node=Node(n)
self.childs.append(node)
return node
def __str__(self):
return '<'+str(self.data)+'>'
def __iter__(self):
yield self #1
for n in self.childs:
for nn in n.__iter__():
yield nn #2

Nope. There have been numerous discussions about this, introducing
something like a yield_all-keyword or such thing that would replace the
above boilerplate - but so far they all have been rejected. Search the
archives for the reasons, I don't remember them :)

Diez
 
P

Paul Hankin

Someone knows if it's possible to make this __iter__ function with just  
one 'yield' intead of two?
...
     def __iter__(self):
         yield self #1
         for n in self.childs:
             for nn in n.__iter__():
                 yield nn #2

Only one yield and shorter (but not really any simpler):

from itertools import chain

class Node:
...
def __iter__(self):
for x in chain([self], *self.childs):
yield x
 
G

George Sakkis

Someone knows if it's possible to make this __iter__ function with just
one 'yield' intead of two?
...
def __iter__(self):
yield self #1
for n in self.childs:
for nn in n.__iter__():
yield nn #2

Only one yield and shorter (but not really any simpler):

from itertools import chain

class Node:
...
def __iter__(self):
for x in chain([self], *self.childs):
yield x

Actually this doesn't need a yield at all:

class Node:
...
def __iter__(self):
return chain([self], *self.childs)

George
 
P

Paul Hankin

Only one yield and shorter (but not really any simpler):
from itertools import chain
class Node:
    ...
    def __iter__(self):
        for x in chain([self], *self.childs):
            yield x

Actually this doesn't need a yield at all:

class Node:
    ...
    def __iter__(self):
        return chain([self], *self.childs)

The two have slightly different behaviours: without the yield, iter is
called immediately on every node in the tree as the iterators are
built. With yield, iterators are built lazily, giving better
behaviour.

But perhaps it's a defect of chain that it calls iter on all of its
arguments straight away -- would it be better if it only built the
iterators when they're needed?
 
B

Boris Borcic

Paul said:
Someone knows if it's possible to make this __iter__ function with just
one 'yield' intead of two?
...
def __iter__(self):
yield self #1
for n in self.childs:
for nn in n.__iter__():
yield nn #2
Only one yield and shorter (but not really any simpler):
from itertools import chain
class Node:
...
def __iter__(self):
for x in chain([self], *self.childs):
yield x
Actually this doesn't need a yield at all:

class Node:
...
def __iter__(self):
return chain([self], *self.childs)

The two have slightly different behaviours: without the yield, iter is
called immediately on every node in the tree as the iterators are
built. With yield, iterators are built lazily, giving better
behaviour.

generator expressions allow to save on the 'yield' while keeping the lazy
behavior, with either :

def __iter__(self):
return chain([self],(y for x in self.childs for y in x))

or

def __iter__(self):
return (y for x in chain([[self]],self.childs) for y in x)

BB
 
L

Lie

Hello!
Someone knows if it's possible to make this __iter__ function with just
one 'yield' intead of two?
Is there some simpler way to make this __iter__ iter through all nodes?
Thanks!

class Node:
def __init__(self, data=None):
self.childs=[]
self.data=data
def appendNode(self, n):
node=Node(n)
self.childs.append(node)
return node
def __str__(self):
return '<'+str(self.data)+'>'
def __iter__(self):
yield self #1
for n in self.childs:
for nn in n.__iter__():
yield nn #2

n=Node()
n.appendNode(1).appendNode(2).appendNode(3).appendNode(4)
n.appendNode(11).appendNode(22).appendNode(33).appendNode(44)
for node in n:
print node

Technically, the root node isn't a child node, and thus it shouldn't
show up in the iteration. I think a more semantically correct way for
this is to have the __str__() returns the current Node + All
Descendants Nodes (like the one you wanted for __iter__) and while
__iter__ only yields the child nodes (used by the __str__ to iterate
through itself), like this:

class Node:
def __init__(self, data=None):
self.childs=[]
self.data=data
def appendNode(self, n):
node=Node(n)
self.childs.append(node)
return node
def __str__(self):
## Returns root node + all descendants
return '<%s>\n' % self.data +
''.join(str(child) for child in self)
def __iter__(self):
## yields childrens only
for n in self.childs:
yield n

n=Node()
n.appendNode(1).appendNode(2).appendNode(3).appendNode(4)
## Note I added the line below for testing branches in
## lower nodes
n.childs[0].appendNode(222)
n.appendNode(11).appendNode(22).appendNode(33).appendNode(44)
print n

The missing functionality of returning current Node's name can be
easily solved by adding it in another function.

The main problem with __iter__ behavior you originally wanted is that
it doesn't reflect the nesting behavior of the Node class and you
could've been equally well served by using flat data structure if you
do that. I smell a bad data structure design here, you'd better revise
your design.

To reflect the nesting, you could do it like this:

class Node:
def __init__(self, data=None):
self.childs=[]
self.data=data
def appendNode(self, n):
node=Node(n)
self.childs.append(node)
return node
def __str__(self):
## This reflects nesting behavior better
return '\n<%s>' % (str(self.data) +
''.join(str(child) for child in self))

## Uncomment this and Comment the statement above
## for an alternate data structure you might be
## interested in, the format below resembles HTML
## curNode = str(self.data)
## return '\n<%s>%s\n</%s>' % (
## curNode,
## ''.join(str(child) for child in self),
## curNode)

def __iter__(self):
for n in self.childs:
yield n

n=Node()
n.appendNode(1).appendNode(2).appendNode(3).appendNode(4)
n.childs[0].appendNode(222)
n.appendNode(11).appendNode(22).appendNode(33).appendNode(44)
print n


This changes the data structure quite a lot though, but the data
structure is bad from the start.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,996
Messages
2,570,238
Members
46,826
Latest member
robinsontor

Latest Threads

Top