I've written the following code to do a pre-order traversal of a Python dict
which may contain other dict
s:
def preorder_traversal(obj):
yield obj
if isinstance(obj, dict):
for k, v in obj.iteritems():
for o in preorder_traversal(v):
yield o
Here are a few examples of its behavior:
> list(preorder_traversal({}))
[{}]
> list(preorder_traversal({'a': 1, 'b': 2}))
[{'a': 1, 'b': 2}, 1, 2]
> list(preorder_traversal({'a': {'c': 1}, 'b': 2}))
[{'a': {'c': 1}, 'b': 2}, {'c': 1}, 1, 2]
It's possible that the tree could get very large, so I'd like to add a mechanism whereby the consumer of the pre-order traversal can abort the search on an entire subtree.
Here's the code I came up with, including a nose test case. The test fails, as described below.
class IgnoreSubtree(Exception):
pass
def preorder_traversal(obj):
try:
yield obj
except IgnoreSubtree:
return
if isinstance(obj, dict):
for k, v in obj.iteritems():
iterator = preorder_traversal(v)
for o in iterator:
try:
yield o
except IgnoreSubtree as e:
try:
iterator.throw(e)
except StopIteration: # WHY?
pass
def test_ignore_subtree():
obj = {'a': {'c': 3}, 'b': 2}
iterator = preorder_traversal(obj)
out = []
for o in iterator:
out.append(o)
if o == {'c': 3}:
iterator.throw(IgnoreSubtree)
eq_([obj, {'c': 3}, 2], out)
The test fails with the following error:
AssertionError: [{'a': {'c': 3}, 'b': 2}, {'c': 3}, 2] !=
[{'a': {'c': 3}, 'b': 2}, {'c': 3}]
i.e. the IgnoreSubtree
has also aborted the iteration over k/v pairs in the top-level object, it hasn't just pruned out the {'c': 3}
subtree.
What's wrong with this code? And why is StopIteration being thrown in the commented location above? Is this a reasonable way to implement subtree pruning for this function, or is there a better way to go about it?