I have a tree structure of objects. I need to iterate over all the items ("values") in the leaves. For this I'm currently using generator methods as illustrated below:
class Node(object):
def __init__(self):
self.items = [Leaf(1), Leaf(2), Leaf(3)]
def values(self):
for item in self.items:
for value in item.values():
yield value
class Leaf(object):
def __init__(self, value):
self.value = value
def values(self):
for i in range(2):
yield self.value
n = Node()
for value in n.values():
print(value)
This prints:
1
1
2
2
3
3
Now, the values returned by a Leaf
will depend on an external parameter. I was thinking of employing coroutines to be able to pass this parameter down to the leaf nodes:
import itertools
class Node2(object):
def __init__(self):
self.items = [Leaf2(1), Leaf2(2), Leaf2(3)]
def values(self):
parameter = yield
for item in self.items:
item_values = item.values()
next(item_values) # advance to first yield
try:
while True:
parameter = (yield item_values.send(parameter))
except StopIteration:
pass
class Leaf2(object):
def __init__(self, value):
self.value = value
def values(self):
parameter = yield
try:
for i in range(2):
parameter = (yield '{}{}'.format(self.value, parameter))
except StopIteration:
pass
n2 = Node2()
values2 = n2.values()
next(values2) # advance to first yield
try:
for i in itertools.count(ord('A')):
print(values2.send(chr(i)))
except StopIteration:
pass
This code is far from pretty, but it works. It prints:
1A
1B
2C
2D
3E
3F
There's a problem with this solution though. I was using itertools.tee
(and chain
) extensively to easily save the state of the iterator in case I needed to backtrack. I was hoping these would work on coroutines as well, but alas, no such luck.
Some alternative solutions I'm considering at the moment:
The first option seems the most attractive. But perhaps there are better options?
Some context: I'm using this construct in RinohType where the tree is formed by MixedStyledText
(node) and SingleStyledText
(leaf) objects. The spans()
methods yield SingleStyledText
instances. The latter can be dependent on external parameters. For example, the page number they are being rendered to. These are currently treated as a special case.
To the extent possible, I like to make functions return simple data structures. In this case,
RinohType
's case, a SingleStyledTextConfig
object or namedtuple
). This implementation will play nicely with itertools
, as before, since it just transforms data into other data.RinohType
's case, the SingleStyledText
object).At a higher level: your proposed solution (as I understand your simplified version) is trying to do too much stuff at the same time. It's trying to configure object creation, tweak that configuration, and create objects based on that configuration all in one step. Your implementation will be simpler, play nicer with itertools
, and be easier to test if you separate these concerns.
For a more detailed treatment of this kind of thinking, see Gary Bernhardt's Boundaries talk and Brandon Rhodes' talk on the Clean Architecture in Python (and of course the resources they mention in the talks).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With