一个python lambda / fn可以代表一个任意调用者吗?

时间:2014-01-22 07:19:29

标签: python lambda generator yield

更新:示例现在列出了所需的结果(下方以粗体显示)

我发现自己编写了大量搜索一些数据的函数,我想让调用者在找到匹配项时指定行为:它们可能会打印出一些内容或将其添加到其中一个数据结构中,但它也是非常需要的。能够选择性地返回找到的数据,以便进一步传输,存储或处理。

实施例

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

问题/问题

这些“解决方案”不仅笨拙 - 需要来自“查找”例程的显式内置支持 - 它们会从访问者可以回馈给顶级调用者的结果集中删除哨兵值...

在简洁,直观,灵活,优雅等方面有更好的选择吗?

谢谢!

4 个答案:

答案 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

当我们想要消费时,您或您的客户可以使用列表推导,mapfilter之类的组合,或只是简单的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。)。