2017-07-06 45 views
23

我目前正在编写一个项目,需要使用返回自身迭代器的方法的第三方代码,这是我在代码中看起来如何的示例:递归地调用一个返回自身迭代器的对象方法

def generate(): 
    for x in obj.children(): 
     for y in x.children(): 
      for z in y.children(): 
       yield z.thing 

目前,这简直让我的代码杂乱无章,而且在3个级别之后变得难以阅读。理想情况下,我会得到它做这样的事情:

x = recursive(obj, method="children", repeat=3).thing 

是否有内置的方式来做到这一点在Python?

回答

24

从python3.3开始,您可以使用yield from语法生成整个生成器表达式。

所以,你可以修改你的函数一点,采取了几个参数:

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) 

yield from表达将产生递归调用的整个发电机表达。

调用你的函数是这样的:

x = generate(obj, 3) 

注意它返回你的x.things发电机。


根据您的特殊要求,这是一个使用getattr与任意属性的作品更宽泛的版本。

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) 

现在,调用函数为:

x = generate(obj, 'children', 'thing', 3) 
+0

尽管如此,通用的解决方案,但这可能在这种情况下工作,但没有一个内置的方式已经做到这一点? – Paradoxis

+0

我的意思更多,因为动态调用'children'和'x.thing' – Paradoxis

+0

@Paradoxis你确实可以。假设'children'返回一个迭代。让我修改我的答案。 –

5

上面的例子yield from是好的,但我严重怀疑是需要的水平/深度PARAM。对于任何树工作的简单/更通用的解决方案:

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

返回:

$ python3 test.py 
['mr.', 'derek', 'curtis', 'anderson'] 

注意这将当前节点的thing之前任何儿童的回报。 (IE会在走下坡路的时候表现自己。)如果您希望在备份步行路线时表现自己,请交换iffor陈述。 (DFS vs BFS)但在你的情况下可能并不重要(我怀疑节点有一个thing或孩子,从来都没有)。

5

如果使用Python 2。7,你需要保持自己的iterables的堆栈,做循环:

from operator import methodcaller 

def recursive(obj, iterater, yielder, depth): 
    iterate = methodcaller(iterater) 
    xs = [iterate(obj)] 
    while xs: 
     try: 
      x = xs[-1].next() 
      if len(xs) != depth: 
       xs.append(iterate(x)) 
      else: 
       yield getattr(x, yielder) 
     except StopIteration: 
      xs.pop() 

这从迭代函数更一般的递归ichain的专业情况:

def recursive_ichain(iterable_tree): 
    xs = [iter(iterable_tree)] 
    while [xs]: 
     try: 
      x = xs[-1].next() 
      if isinstance(x, collections.Iterable): 
       xs.append(iter(x)) 
      else: 
       yield x 
     except StopIteration: 
      xs.pop() 

及一些测试对象:

class Thing(object): 
    def __init__(self, thing): 
     self.thing = thing 

class Parent(object): 
    def __init__(self, *kids): 
     self.kids = kids 

    def children(self): 
     return iter(self.kids) 

test_obj = Parent(
    Parent(
     Parent(Thing('one'), Thing('two'), Thing('three')), 
     Parent(Thing('four')), 
     Parent(Thing('five'), Thing('six')), 
    ), 
    Parent(
     Parent(Thing('seven'), Thing('eight')), 
     Parent(), 
     Parent(Thing('nine'), Thing('ten')), 
    ) 
) 

并对其进行测试:

>>>for t in recursive(test_obj, 'children', 'thing', 3): 
>>> print t 
one 
two 
three 
four 
five 
six 
seven 
eight 
nine 
ten 

Personnaly我倾向于将yield getattr(x, yielder)更改为yield x以访问叶对象本身并显式访问该对象。即

for leaf in recursive(test_obj, 'children', 3): 
    print leaf.thing