У меня есть генератор, который выдает узлы из направленного ациклического графа (DAG), сначала глубина:
def depth_first_search(self):
yield self, 0 # root
for child in self.get_child_nodes():
for node, depth in child.depth_first_search():
yield node, depth+1
Я могу перебирать узлы, как это
for node, depth in graph.depth_first_search():
# do something
Я хотел бы сказать генератору из цикла for, чтобы он не углублялся в график, если выполняется какое-либо условие.
Я пришел к следующему решению, которое использует внешнюю функцию.
def depth_first_search(self, stop_crit=lambda n,d: False):
yield self, 0 # root
for child in self.get_child_nodes():
for node, depth in child.depth_first_search():
yield node, depth+1
if stop_crit(node, depth): break
Это решение заставляет меня объявлять переменные, в которых я нуждаюсь, до определения stop_crit, чтобы к ним можно было получить доступ.
В Ruby yield возвращает последнее выражение из блока, чтобы его можно было удобно использовать для указания генератору продолжить или остановить.
Каков наилучший способ достижения этой функциональности в Python?
4 ответа
Наивное решение:
def depth_first_search(self):
yield self, 0 # root
for child in self.get_child_nodes():
for node, depth in child.depth_first_search():
if(yield node, depth+1):
yield None # for .send
return
Вы можете вызвать его как обычно, но вы должны сохранить итерацию для прерывания:
it = graph.depth_first_search()
for node, depth in it: #this is why there should be pronouns for loop iterables
stuff(node,depth)
if quit: it.send(1)
# it.next() should raise StopIteration on the next for iteration
Я думаю, что это работает прямо сейчас.
Сопрограммы (о них упомянул бас) сложно для непосвященных, так что вот один. Я добавил тестовый код, чтобы вы могли увидеть, как это действительно работает.
class Node(object):
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
# the producing coroutine, it sends data to the consumer
def depth_first_search(self, consumer, depth=0):
""" `consumer` is a started coroutine that yields True to continue a branch """
if consumer.send((self, depth)): # continue this branch?
for child in self.get_child_nodes():
child.depth_first_search(consumer, depth+1)
def get_child_nodes(self):
for node in (self.left, self.right):
if node is not None:
yield node
def __repr__(self):
return "Node(val=%d)" % self.val
def coroutine(func):
""" decorator that declares `func` as a coroutine and starts it """
def starter(*args, **kwargs):
co = func(*args, **kwargs)
next(co) # corotines need to be started/advanced to the first yield
return co
return starter
# the consumer takes data and yields if it wants to continue
@coroutine
def consumer( continue_branch=lambda n,d:True ):
node, depth = (yield True) # first node
while True:
print node, depth # do stuff
node, depth = (yield continue_branch(node, depth))
# testing
tree = Node(5, Node(2, Node(3), Node(4)), Node(6, Node(7), Node(8))) #
cons = consumer()
tree.depth_first_search(cons)# yields all
print
stopper = consumer(lambda n,d: n.val > 2) # skips the children of Node 2
tree.depth_first_search(stopper)
Хитрость в том, что если вы сохраняете роли своих функций, где depth_first_search
дает узлы, вы в конечном итоге получаете ужасный беспорядок ... вместо этого узлы создаются и отправляются потребителю ,
Поддержка сопрограмм в Python немного неуклюжа (@coroutine
на помощь). Существует хороший хороший учебник по Python и множество ресурсов для языков, которые зависят от сопрограмм, таких как Lua . В любом случае, это очень крутая концепция, которую стоит изучить :-)
Обычно в Python вы просто прекращаете использовать генератор и забываете об этом. Точка . (Таким образом, оставляя вещи для сборщика мусора обычным способом)
Тем не менее, используя generator.close()
, вы можете принудительно произвести немедленную очистку генератора, немедленно запустив все финализации.
Примере:
>>> def gen():
... try:
... for i in range(10):
... yield i
... finally:
... print "gen cleanup"
...
>>> g = gen()
>>> next(g)
0
>>> for x in g:
... print x
... if x > 3:
... g.close()
... break
...
1
2
3
4
gen cleanup
>>> g = gen()
>>> h = g
>>> next(g)
0
>>> del g
>>> del h # last reference to generator code frame gets lost
gen cleanup
Обычно вы не говорите своему итерируемому проверять условия, вы делаете это в теле вашего цикла:
for node, depth in graph.depth_first_search():
if node meets condition:
# do something with node
break
# do something with node, its still referencing what you breaked on
Этот код имеет то преимущество, что никого не удивляет и не смущает.
Похожие вопросы
Новые вопросы
python
Python - это многопарадигмальный, динамически типизированный, многоцелевой язык программирования. Он разработан для быстрого изучения, понимания и использования, а также для обеспечения чистого и единообразного синтаксиса. Обратите внимание, что Python 2 официально не поддерживается с 01.01.2020. Тем не менее, для вопросов о Python, связанных с версией, добавьте тег [python-2.7] или [python-3.x]. При использовании варианта Python (например, Jython, PyPy) или библиотеки (например, Pandas и NumPy) включите его в теги.