I'm currently writing a project that requires third party code that uses a method that returns an iterator of itself, an example of how this would look in my code:
def generate(): for x in obj.children(): for y in x.children(): for z in y.children(): yield z.thing
Currently this simply clutters my code, and becomes hard to read after 3 levels. Ideally I'd get it to do something like this:
x = recursive(obj, method="children", repeat=3).thing
Is there a built in way to do this in Python?
Recursion and iteration are both different ways to execute a set of instructions repeatedly. The main difference between these two is that in recursion, we use function calls to execute the statements repeatedly inside the function body, while in iteration, we use loops like “for” and “while” to do the same.
The __iter__() method returns the iterator object itself. If required, some initialization can be performed. The __next__() method must return the next item in the sequence. On reaching the end, and in subsequent calls, it must raise StopIteration .
Starting from python3.3, you can use the yield from
syntax to yield an entire generator expression.
So, you can modify your function a bit, to take a couple of parameters:
def generate(obj, n): if n == 1: for x in obj.children(): yield x.thing else: for x in obj.children(): yield from generate(x, n - 1)
The yield from
expression will yield the entire generator expression of the recursive call.
Call your function like this:
x = generate(obj, 3)
Note that this returns you a generator of x.things
.
Based on your particular requirement, here's a more generic version using getattr
that works with arbitrary attributes.
def generate(obj, iterable_attr, attr_to_yield, n): if n == 1: for x in getattr(obj, iterable_attr): yield getattr(x, attr_to_yield) else: for x in getattr(obj, iterable_attr): yield from generate(x, iterable_attr, attr_to_yield, n - 1)
And now, call your function as:
x = generate(obj, 'children', 'thing', 3)
The yield from
example above is good, but I seriously doubt the level/depth param is needed. A simpler / more generic solution that works for any tree:
class Node(object): def __init__(self, thing, children=None): self.thing = thing self._children = children def children(self): return self._children if self._children else [] def generate(node): if node.thing: yield node.thing for child in node.children(): yield from generate(child) node = Node('mr.', [Node('derek', [Node('curtis')]), Node('anderson')]) print(list(generate(node)))
Returns:
$ python3 test.py ['mr.', 'derek', 'curtis', 'anderson']
Note this will return the current node's thing
before any of its children's. (IE it expresses itself on the way down the walk.) If you'd prefer it to express itself on the way back up the walk, swap the if
and the for
statements. (DFS vs BFS) But likely doesn't matter in your case (where I suspect a node has either a thing
or children, never both).
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