2

在很多情况下,人们都在说“使用yield懒惰创建元素”。但我认为一切都有成本,包括yield和它的迭代器。

在有效的北方人眼中,我认为这是一个很好的问题。所以,例如,当我得到一个函数时。

def list_gen(n):
    if n > MAGIC_NUM:
        return xrange(n)
    else:
        return range(n)

MAGIC_NUM 的剂量是多少?

更新对不起这个错误,我的原意是比较迭代器的成本和列表成本。

再次更新请对案例进行成像。是否有一个条件,即内存如此有限以至于无法创建迭代器。

哈,这个问题现在更有趣了。
再次更新为什么创建迭代器并保存产量上下文比创建列表要少?或者迭代器的成本是多少?(对不起我的侮辱)多少字节?

4

5 回答 5

4

你把几件事混在一起了。

def list_gen(n):
    i=0
    while i<n:
        yield i
        i += 1

这个函数是一个生成器。调用它会返回一个生成器对象,它是一个迭代器

迭代器是具有 的事物next(),即它可以被遍历一次。每当iter您执行for i in something.

def list_gen(n):
    return range(n)

def list_gen(n):
    return xrange(n)

这些函数是常规函数。一个返回一个list,另一个返回一个xrange对象。列表和 xrange 都是可迭代的,即可以为它们创建多个独立的迭代器。


所以回到你的问题:你问是返回一个list还是一个xrange对象。

这取决于,显然!这取决于你想对结果做什么。

  • 如果你想以某种方式对其进行变异,那么你需要一个真实的列表。直接使用range

  • 如果您只想迭代它,那么它在语义上并没有什么不同:xrange对象和list返回的对象range都会产生一个迭代器,该迭代器迭代相同的序列。

    但是,如果您使用xrange,您将永远不会在内存中创建整个列表。list如果你想做的只是一个简单的迭代,为什么还要在内存中创建一个成熟的对象呢?您不需要在需要for循环时分配临时的大内存缓冲区,对吗?

因此:坚持使用 是安全的xrange,因为调用者总是可以从中获利list


让我们用一个基准来确认这一点。我们想知道迭代 xranges 是否比迭代 by 构造的列表更快range(当然包括range调用成本)。

代码:

import timeit

ns = [1,2,3, 5, 10, 50, 100]
print 'n', '\t', 'range', '\t', 'xrange'
for n in ns:
    t1 = timeit.timeit("for i in range({}): pass".format(n))
    t2 = timeit.timeit("for i in xrange({}): pass".format(n))
    print n, '\t', t1, '\t', t2

结果:

n       range           xrange
1       0.566222990493  0.418698436395
2       0.594136874362  0.477882061758
3       0.630704800817  0.488603362929
5       0.725149288913  0.540597548519
10      0.90297752809   0.687031507818
50      2.44493085566   1.89102105759
100     4.31189321914   3.33713522433
于 2012-11-29T09:46:40.967 回答
3

它与您生成的迭代器的长度无关,而是与您之后需要如何使用它有关。如果您只需要使用一次,那么您绝对应该选择yield,如果您要多次使用它,您可以跳过yield并获得一个常规列表。请记住,您使用 yield 获得的生成器只能迭代一次

于 2012-11-29T09:30:21.243 回答
3

尽管您的问题及其标题仍然有些混淆,但我会尝试以我理解的方式回答。

如果您只想遍历 的结果,对于更短和更长的范围(x)range()xrange()(特殊对象)比range()(列表)更好:

$ python -m timeit 'a=range(3)' 'for i in a: pass'
1000000 loops, best of 3: 0.608 usec per loop
$ python -m timeit 'a=xrange(3)' 'for i in a: pass'
1000000 loops, best of 3: 0.466 usec per loop

$ python -m timeit 'a=xrange(30000)' 'for i in a: pass'
1000 loops, best of 3: 1.01 msec per loop
$ python -m timeit 'a=range(30000)' 'for i in a: pass'
1000 loops, best of 3: 1.49 msec per loop

所以xrange()总是使用会更好。


如果您看一下一般情况,可能会略有不同:您比较“预生产”值/对象,将它们存储在列表中,然后在生产后直接使用它们进行处理:

def gen(num):
    import random
    i = 0
    while i < num:
        value = random.random()
        yield value
        i += 1

def process(value): pass

def test1(num):
    data = list(gen(num))
    for i in data: process(num)

def test2(num):
    for i in gen(num): process(num)

这取决于生产和消费如何相互作用,以及开销有多大。

如果您希望它们独立行动,您可以使用线程“同时执行两者”:

def list_eater(l):
    while l:
        yield l.pop(0)
def test3(num):
    data = []
    def producer():
        for i in gen(num): data.append(i)
    import threading
    consumerthread = threading.Thread(target=producer)
    consumerthread.start()
    while data or consumerthread.isAlive():
        for item in list_eater(data): process(item)
        # Optimizeable. Does idle waiting; a threading.Condition might be quite useful here...

运行生产并消耗所有物品,因为它们在这里需要生产或消耗它们。

于 2012-11-29T10:00:00.663 回答
2

使用yield或生成器大多与列表大小无关,例如:

  • 如果您不需要处理整个列表并且可能很快就会中断,那么使用生成器会更有效。
  • 模拟无限大小的流,例如素数生成器。

但是,如果您的内存有限,例如嵌入式系统,并且无法立即创建整个列表,则有必要使用生成器。

至于成本,使用生成器有额外的成本,如果你计算每次调用生成器时评估调用的成本,但是使用列表会占用更多内存,所以你不能一般说生成器是比列表更好,因为它涉及内存和性能之间的一些权衡,是否使用生成器取决于您的需求和情况。

于 2012-11-29T09:38:38.800 回答
2

请注意,不能同时使用yieldreturn。函数可以是生成器函数或普通函数,但不能同时是两者。

通常yield避免创建中间列表,而是一个一个地产生元素。例如,当您递归地走一棵树时,这可能很有用。有关示例,请参见此链接:http: //code.activestate.com/recipes/105873-walk-a-directory-tree-using-a-generator/

生成器的另一种用途是当您想要返回大量元素,但您的用户可能只对前几个元素感兴趣(例如搜索结果)。

避免中间列表将节省内存,但前提是调用者不需要从结果中创建列表。一般来说,优点是它可以让你更简洁地编写你的生成器函数。

于 2012-11-29T09:40:28.333 回答