Рекурсивно вызов метода объекта, который возвращает сам итератор
В настоящее время я пишу проект, для которого требуется сторонний код, который использует метод, который возвращает сам итератор, пример того, как это будет выглядеть в моем коде:
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?
Ответы
Ответ 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