Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic Python: Propagating yields or flattening sequences?

I'm writing a breadth depth-first tree traversal function, and what I want to do is this:

def traverse(node):
    yield node
    for n in node.children:
        yield_all traverse(n) # << if Python had a yield_all statement

The idea is to end up with a (flat) sequence of nodes in the tree.

Approach #1: (propagating yields)

def traverse(node):
    yield node
    for n in node.children:
        for m in traverse(n):
            yield m

Approach #2: (flattening sequences)

def traverse(node):
    return itertools.chain([node],*(traverse(n) for n in node.children))

The first approach seems more clean, but I feel weird explicitly yielding each node in the subtree at each level.

The second approach is terse and slightly dirty, but it matches what I would write in Haskell:

traverse node = node : concatMap traverse (children node)

So my question is: Which is better? Or am I missing a best 3rd option?

like image 299
perimosocordiae Avatar asked Dec 14 '10 21:12

perimosocordiae


3 Answers

[UPDATE] See PEP-380, this yield all syntax is available starting from Python 3.3 as yield from:

def traverse(node):
    yield node
    for n in node.children:
        yield from traverse(n)
like image 197
tokland Avatar answered Nov 15 '22 22:11

tokland


I'd go with first. You'll get over propagating yields after a couple of times. :-)

like image 3
Lennart Regebro Avatar answered Nov 15 '22 23:11

Lennart Regebro


This is an opinions question, so all the answers will just be value judgments. As far as I can think there's no elegant third way, though.

My opinion is that the first way wins hands down. It's clearer and easier to read -- Python isn't Haskell, even though it can do some functional stuff, and often the functional approach just doesn't look as neat.

like image 1
Katriel Avatar answered Nov 15 '22 23:11

Katriel