Python中是否有一个库函数可以将生成器函数转换为返回列表的函数?

34

有好几次我认为生成器的方式比返回列表更加直接,例如:

def foo(input_array):
    for x in input_array:
        yield processed(x)

vs.

def bar(input_array):
    accumulator = []
    for x in input_array:
        accumulator.append(processed(x))
    return accumulator

(好吧,如果真的那么简单,我会写map,但你懂我的意思:生成器版本更简洁)。然而,返回生成器类型并不总是理想的。有没有内置的装饰器可以用来将foo改成返回列表或元组的函数?我自己写的方式是,

import functools

def transform_return_value(transformer):
    def inner(f):
        @functools.wraps(f)
        def new_f(*argv, **kwargs):
            return transformer(f(*argv, **kwargs))
        return new_f
    return inner

@transform_return_value(list)
def foo(input_array):
    for x in input_array:
        yield processed(x)

8
等一下... 你想写一个生成器函数来返回列表?我不确定我理解这个意思。为什么不直接写 list(generator_function()) 呢,或者一开始就写一个 list_function() 函数呢? - mgilson
11
似乎有些人误解了这个问题的要点...既然这是我过去曾经遇到的问题,让我尝试澄清一下:“有些情况下,一个函数可以更加干净利落地作为一个生成器来实现,但是这个函数必须返回一个列表。”显然,有许多合理的方法可以编写返回列表的函数。但是,提问者特别关心如何装饰返回生成器的函数,以便它会返回一个列表。 - David Wolever
3
就实现而言,“yield ...”可以替换为“lst.append(...)”。但在我看来这并没有简化多少…… - mgilson
2
@mgilson,这绝对是一个有效的观点。但评论和答案所关注的并不是这个问题。评论和答案似乎集中在不同的方法上,以获取列表(例如,生成器表达式,在函数调用中包装list(…))...而不是回答OP的实际问题:“Python中是否有将生成器函数转换为返回列表的函数的库函数?” - David Wolever
12
如果我看起来有点沮丧,那是因为我经常处于和提问者类似的境地:我描述了明确的限制并提出了具体问题...但是,我没有得到对问题的回答,而是被告知我的限制是错误的。这虽然可以理解(“你问错问题了”绝对是某些问题的有效答案),但却很令人沮丧。 - David Wolever
显示剩余3条评论
4个回答

28
据我所知(我已经查过了,因为我也曾经想过同样的问题),没有:使用标准库没有直接的方法来实现这一点。
不过,在 unstdlib.py 库中有一个经过充分测试的 listify 包装器:https://github.com/shazow/unstdlib.py/blob/master/unstdlib/standard/list_.py#L149
def listify(fn=None, wrapper=list):
    """
    A decorator which wraps a function's return value in ``list(...)``.

    Useful when an algorithm can be expressed more cleanly as a generator but
    the function should return an list.

    Example::

        >>> @listify
        ... def get_lengths(iterable):
        ...     for i in iterable:
        ...         yield len(i)
        >>> get_lengths(["spam", "eggs"])
        [4, 4]
        >>>
        >>> @listify(wrapper=tuple)
        ... def get_lengths_tuple(iterable):
        ...     for i in iterable:
        ...         yield len(i)
        >>> get_lengths_tuple(["foo", "bar"])
        (3, 3)
    """
    def listify_return(fn):
        @wraps(fn)
        def listify_helper(*args, **kw):
            return wrapper(fn(*args, **kw))
        return listify_helper
    if fn is None:
        return listify_return
    return listify_return(fn)

1
“我复制粘贴在项目中使用的工具库” 我不知道是松了一口气还是感到沮丧,因为我似乎并不是唯一一个这样做的人。我有几个 Python 函数似乎在大多数我的 Python 项目中都有所应用。复制和粘贴看起来很糟糕,但它们太小(而且彼此之间没有关联),将它们放入库中似乎过于麻烦。 - Laurence Gonsalves
啊哈!自从我写下这个答案后,我已经开始编写了一个类似的库:https://github.com/shazow/unstdlib.py - David Wolever
我更愿意将装饰器称为 wrap_return,然后执行 listify = wrap_return(wrapper=list),而不是使用 @listify(wrapper=Counter) - Tadhg McDonald-Jensen

8
尽管 @David Wolever 的回答肯定是最干净的方式,但我经常发现自己会这样做(因为它不需要定义外部装饰器):将生成器作为局部函数编写,像这样:

def foo(input_array):
    def gen():
        for x in input_array:
            yield processed(x)

    return list(gen())

3
这里有一个简单的装饰器,没有任何花哨的东西:
from functools import wraps
from types import GeneratorType

def listify(func):
    """decorator for making generator functions return a list instead"""
    @wraps(func)
    def new_func(*args, **kwargs):
        r = func(*args, **kwargs)
        if isinstance(r, GeneratorType):
            return list(r)
        else:
            return r
    return new_func

-6

为了高效和简洁地定义列表,请尝试使用列表推导式:

def foo(input_array):
    return [processed(x) for x in input_array]

如果你想让一个函数返回一个列表,就让它返回一个列表。这比使用装饰器更加清晰、易于理解、阅读和调试。

你可能更喜欢将其内联编写,而不是调用一个函数。


6
这并没有回答 OP 的问题。OP 描述的情况是在使用生成器实现函数更加简洁的情况下,该函数的返回值必须是一个列表。在这种情况下,将函数实现为生成器,然后再用一个装饰器将该生成器转换为列表是完全合理的。 - David Wolever
@David 使用列表推导式甚至更加简洁! - Andy Hayden
3
是的,这个特定例子用列表推导式更加简洁明了。但问题不是“如何清理这个函数的实现?”,而是“Python中是否有库函数可以将生成器函数转换为返回列表的函数?” - David Wolever
2
Hayden:感谢您愿意提供帮助,但我已经明确说明foo只是一个示例函数(元变量);对于实际的实现,我会在被调用的地方编写map,或者foo = functools.partial(map, processed),或者foo = lambda xs: map(processed, xs)(它们比列表推导更简洁...)。 - gatoatigrado

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接