Подтвердить что ты не робот

Рекурсивно вызов метода объекта, который возвращает сам итератор

В настоящее время я пишу проект, для которого требуется сторонний код, который использует метод, который возвращает сам итератор, пример того, как это будет выглядеть в моем коде:

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

В настоящее время это просто загромождает мой код и становится трудно читать после трех уровней. В идеале я бы сделал так, чтобы сделать что-то вроде этого:

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

Есть ли встроенный способ сделать это в Python?

4b9b3361

Ответ 1

Начиная с 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)

Ответ 2

Приведенный выше пример yield from хорош, но я серьезно сомневаюсь, что параметр уровня/глубины необходим. Более простое/более общее решение, которое работает для любого дерева:

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']

Обратите внимание, что это вернет текущий node thing перед любым из его дочерних элементов. (IE он выражает себя на пути вниз по ходу.) Если вы предпочтете, чтобы он выразил себя на пути назад, поменяйте операторы if и for. (DFS vs BFS) Но, скорее всего, это не имеет значения в вашем случае (где я подозреваю, что node имеет либо thing, либо дети, никогда не оба).

Ответ 3

Если вы используете 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

Personnaly я бы склонен изменить yield getattr(x, yielder) на yield x для доступа к листовым объектам и явным образом получить доступ к этой вещи. то есть.

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