递归调用一个返回自身迭代器的对象方法

26

我目前正在撰写一个需要使用第三方代码的项目,该代码使用返回自身迭代器的方法。以下是该方法在我的代码中的示例:

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中是否有内置的方法来实现这一点?

3个回答

28

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

不过,有没有一种通用的解决方案呢?这种方法可能在这种情况下有效,但是难道没有内置的方法来完成这个任务吗? - Paradoxis
我指的是更多的动态调用 childrenx.thing - Paradoxis
@Paradoxis 没错,可以的。假设 children 返回一个可迭代对象。让我修改我的回答。 - cs95

7

如果使用Python 2.7,您需要保持自己的可迭代对象堆栈并进行循环:

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

个人而言,我倾向于将yield getattr(x, yielder)更改为yield x,以访问叶子对象本身并明确访问该对象。例如:

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

5
上面的 yield from 例子很好,但是我认为不需要 level/depth 参数。一种更简单/通用的解决方案适用于任何树形结构:
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,在其任何子节点之前。(也就是说,在走下树时表达自己。)如果你希望它在走回来的路上表达自己,请交换iffor语句。(深度优先搜索和广度优先搜索)但在您的情况下可能并不重要(我怀疑一个节点只有thing或子节点,从不同时拥有两者)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接