更新:示例现在列出了所需的结果(下方以粗体显示)
我发现自己编写了大量搜索一些数据的函数,我想让调用者在找到匹配项时指定行为:它们可能会打印出一些内容或将其添加到其中一个数据结构中,但它也是非常需要的。能够选择性地返回找到的数据,以便进一步传输,存储或处理。
def find_stuff(visitor): # library search function
for x in (1, 2, 3, 4, 5, 6):
visitor(x)
首次使用客户端:
def my_visitor(x): # client visitor functions (also often use lambdas)
if x > 3:
yield x / 2 #>>> WANT TO DO SOMETHING LIKE THIS <<<#
results = find_stuff(my_visitor) # client usage
results
应该会产生4 / 2,5 / 2,然后是6/2 ......即2,2,3。
第二次客户使用:
def print_repr_visitor(x):
print repr(x)
find_stuff(print_repr_visitor) # alternative usage
应该打印1 2 3 4 5 6(单独的行)但什么都不产生
但是,yield
不会在“结果”中创建一个生成器(至少使用python 2.6.6,我坚持使用)。
我一直在攻击这个,经常这样......
def find_stuff(visitor):
for x in (1, 2, 3, 4, 5):
val = visitor(x)
if val is not None:
yield val
...或者有时,当访问者参数列表很难输入太多次......
def find_stuff(visitor):
for x in (1, 2, 3, 4, 5):
val = visitor(x)
if val == 'yield':
yield x
elif val is not None:
yield val
这些“解决方案”不仅笨拙 - 需要来自“查找”例程的显式内置支持 - 它们会从访问者可以回馈给顶级调用者的结果集中删除哨兵值...
在简洁,直观,灵活,优雅等方面有更好的选择吗?
谢谢!
答案 0 :(得分:6)
在Python 3中,您可以使用yield from
从子生成器中生成项目:
def find_stuff(visitor):
for x in (1, 2, 3, 4, 5):
yield from visitor(x)
在Python 2中,您必须遍历子生成器。这需要更多的代码并且不处理一些边缘情况,但它通常足够好:
def find_stuff(visitor):
for x in (1, 2, 3, 4, 5):
for item in visitor(x):
yield item
边缘情况就像在子生成器中尝试send
值或throw
例外一样。如果您没有使用协同程序功能,您可能不需要担心它们。
答案 1 :(得分:2)
如果理解正确,也许你想要这样的事情:
def find_stuff(visitor):
for x in [1, 2, 3, 4, 5]:
match, val = visitor(x)
if match:
yield val
def my_visitor(x):
if x > 4:
return True, x/2
else:
return False, None
也就是说,让访问者返回两件事:要产生的值(如果有的话)和一个指示是否产生值的布尔值。这样就可以产生任何价值。
您的问题的标题似乎表明您希望my_visitor
以某种方式决定find_stuff
是否在每次迭代中产生一个值,但您实际上并未在问题中描述这一点。无论如何,这是不可能的。一个生成器可以调用另一个函数来决定要产生什么,但是被调用函数没有办法神奇地使它的调用者产生或不产生;该决定必须在调用者中进行(在这种情况下为find_stuff
)。
但是,从你的问题来看,我不明白为什么这是一个问题。你说你提出的解决方案是“笨拙的 - 需要从”发现“程序中明确的内置支持”,但我不知道这是多么笨拙。它只是一个API。 find_stuff
显然必须有“内置支持”才能完成它应该做的事情,并且访问者必须知道返回与呼叫者进行通信的内容。您不能期望能够编写一个my_visitor
函数,该函数适用于任何人可能提出的任何查找例程;系统作为一个整体必须定义一个API,描述如何编写find_stuff
可以使用的访问者。因此,您只需要提供访问者必须遵循的API。我上面的例子是一个简单的API,但很难从你的问题中判断出你在寻找什么。
答案 2 :(得分:2)
我确实通过一些调查找到了解决方案,并在python 2.6中。这有点奇怪,但确实有效。
from itertools import chain
def my_visitor(x):
if x > 3:
yield x / 2
def find_stuff(visitor):
search_list = (1,2,3,4,5,6)
return (x for x in chain.from_iterable(visitor(x) for x in search_list))
find_stuff(my_visitor)
<generator object <genexpr> at 0x0000000047825558>
list(find_stuff(my_visitor))
[0x2, 0x2, 0x3]
正如所料。发电机很好,因为你可以做这样的事情:
def my_visitor2(x):
if x > 3:
yield x / 2
elif x > 1:
yield x
yield x*2
yield x-3
In [83]: list(find_stuff(my_visitor2))
[0x2, 0x4, -0x1, 0x3, 0x6, 0x0, 0x2, 0x2, 0x3]
并且每次访问都不返回任何值,单个值或一堆值,并且它们都会进入结果。
您也可以将其调整为标量值。最好的方法是使用嵌套生成器:
sentinel = object()
def my_scalar_visitor(x):
if x > 3:
return x / 2
else:
return sentinel
def find_stuff_scalar(scalar_visitor):
search_list=(1,2,3,4,5,6)
return (x for x in (scalar_visitor(y) for y in search_list) if x != sentinel)
list(find_stuff_scalar(my_scalar_visitor))
[0x2, 0x2, 0x3]
答案 3 :(得分:1)
user2357112's answer解决了问题给出的问题,但在我看来,生成器内的生成器方法在这种特定情况下过于复杂,并限制了客户端使用代码的选项。
您希望遍历某些结构,应用某些函数并生成结果。您的代码允许这样做,但是您正在混淆Python已经拥有的两个优点,单独支持(遍历和映射)而没有额外的好处。
您的遍历功能可以简单地遍历:
def traverse_stuff():
for x in (1, 2, 3, 4, 5, 6):
yield x
当我们想要消费时,您或您的客户可以使用列表推导,map
和filter
之类的组合,或只是简单的for
循环:
[x / 2 for x in traverse_stuff() if x > 3]
map(lambda x: x / 2, filter(lambda x: x > 3, traverse_stuff())
for value in traverse_stuff():
print(value)
以这种方式拆分代码使其更易于组合(您的客户端不仅限于访问者模式/生成器),对于其他Python开发人员更直观,并且对于您只需要消耗部分结构的情况更具性能(例如,当您只需要从树中找到n个节点时,当您只想在结构中找到满足条件的第一个值时,&amp; c。)。