Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursively calling an object method that returns an iterator of itself

Tags:

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?

like image 584
Paradoxis Avatar asked Jul 06 '17 12:07

Paradoxis


People also ask

Is recursive function iterative?

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.

What does __ Iter__ method returns in Python?

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 .


2 Answers

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) 
like image 78
cs95 Avatar answered Oct 09 '22 01:10

cs95


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).

like image 44
keredson Avatar answered Oct 08 '22 23:10

keredson