I implemented graph traversal as a generator function which yields the node being visited.
Sometimes the user needs to tell the traversal function that the edges outgoing from a particular node shouldn't be followed; in order to support that, the traversal checks the value sent back to it (using generator send()
method), and if it's True
, regards the node as a leaf for traversal purposes.
The problem is that the simplest user loop is kinda long:
# simplified thanks to @tobias_k
# bfs is the traversal generator function
traversal = bfs(g, start_node)
try:
n = next(traversal)
while True:
# process(n) returns True if don't want to follow edges out of n
n = traversal.send(process(n))
except StopIteration:
pass
Is there any way to improve this?
I thought something like this should work:
for n in bfs(g, start_node):
???.send(process(n))
but I feel I'm missing the knowledge of some python syntax.
答案 0 :(得分:5)
I don't see a way to do this in a regular for
loop. However, you could create another generator, that iterates another generator, using some "follow-function" to determine whether to follow the current element, thus encapsulating the tricky parts of your code into a separate function.
def checking_generator(generator, follow_function):
try:
x = next(generator)
while True:
yield x
x = generator.send(follow_function(x))
except StopIteration:
pass
for n in checking_generator(bfs(g, start_node), process):
print(n)
答案 1 :(得分:3)
要简化客户端代码,您可以使用普通的bsf()
生成器并检查其中的node.isleaf
属性:
for node in bfs(g, start_node):
node.isleaf = process(node) # don't follow if `process()` returns True
缺点是node
是可变的。或者您必须传递跟踪叶节点的共享数据结构:leaf[node] = process(node)
其中leaf
字典早先传递到bfs()
。
如果要明确使用.send()
方法;你必须处理StopIteration
。见PEP 479 -- Change StopIteration handling inside generators。您可以将其隐藏在辅助函数中:
def traverse(tree_generator, visitor):
try:
node = next(tree_generator)
while True:
node = tree_generator.send(visitor(node))
except StopIteration:
pass
示例:
traverse(bfs(g, start_node), process)
答案 2 :(得分:1)
我不认为这是一个常见的用例,将其视为原始生成器:
def original_gen():
for x in range(10):
should_break = yield x
if should_break:
break
如果should_break
的值总是基于x
的某个函数调用计算,那么为什么不这样写这样的生成器:
def processing_gen(check_f):
for x in range(10):
yield x
should_break = check_f(x)
if should_break:
break
但是我通常会想到将生成的值处理为在循环内部写入的代码(否则有一个循环的重点是什么?)
你真正想要做的是创建一个生成器,调用__next__
方法真正暗示send(process(LAST_VALUE))
可以用类实现:
class Followup_generator(): #feel free to use a better name
def __init__(self,generator,following_function):
self.gen = generator
self.process_f = following_function
def __iter__(self):
return self
def __next__(self):
if hasattr(self,"last_value"):
return self.send(self.process_f(self.last_value))
else:
self.last_value = next(self.gen)
return self.last_value
def send(self,arg):
self.last_value = self.gen.send(arg)
return self.last_value
def __getattr__(self,attr):
"forward other lookups to the generator (.throw etc.)"
return getattr(self.gen, attr)
# call signature is the exact same as @tobias_k's checking_generator
traversal = Followup_generator(bfs(g, start_node), process)
for n in traversal:
print(n)
n = traversal.send(DATA) #you'd be able to send extra values to it
然而,这仍然没有经常使用,我对while
循环完全没问题,尽管我将.send
调用放在顶部:
traversal = bfs(g, start_node)
send_value = None
while True:
n = traversal.send(send_value)
#code for loop, ending in calculating the next send_value
send_value = process(n)
你可以将它包装在try: ... except StopIteration:pass
中,虽然我发现只需等待错误提升就可以用上下文管理器更好地表达出来:
class Catch:
def __init__(self,exc_type):
if issubclass(exc_type,BaseException):
self.catch_type = exc_type
else:
raise TypeError("can only catch Exceptions")
def __enter__(self):
return self
def __exit__(self,exc_type,err, tb):
if issubclass(exc_type, self.catch_type):
self.err = err
return True
with Catch(StopIteration):
traversal = bfs(g, start_node)
send_value = None
while True:
n = traversal.send(send_value)
#code for loop, ending in calculating the next send_value
send_value = process(n)
答案 3 :(得分:0)
我发现我的问题会有一个单行的答案,使用扩展的"继续" earlier version of PEP 342中提出的陈述:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R=301]
RewriteCond %{REQUEST_URI} !(\.css|\.js|\.png|\.jpg|\.gif|robots\.txt)$ [NC]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^(css|js|images)/(.*)$ public/$1/$2 [L,NC]
然而,当PEP 342被接受时,该特定功能在Raymond和Guido之间this June 2005 discussion之后被撤销:
Raymond Hettinger说:让我作为一个强大的-1记录下来,继续EXPR"。该 for循环是我们最基本的结构,很容易理解 现在的形式。对于"继续"同样可以说和"休息"哪一个 为人们提供近零学习曲线的额外优势 从其他语言迁移。
任何使这些基本陈述复杂化的冲动都应该是认真的 仔细检查并保持高标准的清晰度,可解释性, 显而易见,有用和必要。国际海事组织,它失败了大部分 测试
我不希望解释"继续EXPR"在教程中 并认为它会成为一个反特征。
[...]正确反对"继续EXPR"是那里 还没有用例;如果有一个好的用例,解释 会轻易跟进。
圭
如果python核心开发人员已经改变了他们关于扩展"继续"的有用性的想法,也许这可以重新引入到未来的PEP中。但是,鉴于这个问题几乎完全相同的用例已在引用的帖子中讨论过,并且没有发现具有说服力,但似乎不太可能。
答案 4 :(得分:0)
这可能是该主题的问题的答案。
看看traversal
函数和自定义send
函数中其他空的yields语句,它可以完成神奇的工作。
# tested with Python 3.7
def traversal(n):
for i in range(n):
yield i, '%s[%s] %s' % (' ' * (4 - n), n, i)
stop = yield
if stop:
yield # here's the first part of the magic
else:
yield # the same as above
yield from traversal(int(n / 2))
def send(generator, value):
next(generator) # here's the second part of the magic
generator.send(value)
g = traversal(4)
for i, (num, msg) in enumerate(g):
print('>', i, msg)
stop = num % 2 == 0
send(g, stop)
答案 5 :(得分:0)
我编写了一个small class SettableGenerator
,它使用一种方法来接收要发送的值,然后在调用__next__
时将其转发给实际的生成器。
您可以这样写:
gen = SettableGenerator(bfs(g, start_node))
for n in gen:
gen.set(process(n))
答案 6 :(得分:0)
让我们考虑以下生成器。它生成从0到9的数字。对于每个生成的数字,它都会得到一个输入并将其存储到ret
中:
def count_to_nine():
# Output: numbers from 0 to 9
# Input: converted numbers
ret = []
for i in range(10):
# Yield a number, get something back
val = (yield i)
# Remember that "something"
ret.append(val)
return ret
实际上,您可以使用next()
+ send()
进行迭代,
但是最好的方法是单独使用send()
进行迭代:
g = count_to_nine()
value = None # to make sure that the first send() gives a None
while True:
value = g.send(value) # send the previously generated value, get a new one
value = f'#{value}'
结果如下:
StopIteration:['#0','#1','#2','#3','#4','#5','#6','#7','#8' ,'#9']
如果需要该输出,请捕获StopIteration
并从中获取结果。
干杯!