嵌套python生成器的速度有多快?

嵌套python生成器的速度有多快?,python,generator,Python,Generator,好的,所以我可能不应该担心这个问题,但是我有一些代码,可以通过一组过滤器、映射和其他东西传递一个(可能很长,可能很短)可能性列表,我想知道我的实现是否会执行得很好 作为我想做的事情的一个例子,考虑一下这个操作链: 获取从1到100的所有数字 只保留偶数 将每个数字平方 生成上面列表中的i和[1,2,3,4,5]中的j的所有对[i,j] 仅保留i+j>40的配对 现在,在做了所有这些废话之后,我想通过这组对[I,j]来寻找一个满足一定条件的对。通常,解决方案是第一个条目之一,在这种情况下,我甚

好的,所以我可能不应该担心这个问题,但是我有一些代码,可以通过一组过滤器、映射和其他东西传递一个(可能很长,可能很短)可能性列表,我想知道我的实现是否会执行得很好

作为我想做的事情的一个例子,考虑一下这个操作链:

  • 获取从1到100的所有数字
  • 只保留偶数
  • 将每个数字平方
  • 生成上面列表中的i和[1,2,3,4,5]中的j的所有对[i,j]
  • 仅保留i+j>40的配对
现在,在做了所有这些废话之后,我想通过这组对[I,j]来寻找一个满足一定条件的对。通常,解决方案是第一个条目之一,在这种情况下,我甚至不看任何其他条目。然而,有时我不得不使用整个列表,却找不到答案,不得不抛出一个错误

我想将我的“操作链”实现为一个生成器序列,即,每个操作迭代通过前一个生成器生成的项,并逐项“生成”自己的输出(一个SICP流)。这样,如果我从不查看输出的最后300个条目,它们甚至不会被处理。我知道itertools提供了类似imap和ifilter的功能,用于执行我想要执行的许多类型的操作


我的问题是:在我必须遍历所有可能的情况下,一系列嵌套生成器是否会对性能造成重大影响?

根据官方文档,使用生成器表达式基本上等同于调用
imap
,因为它创建了一个迭代器。(“”)没有明确讨论嵌套表达式是创建单独(组合)的对象,还是创建内部逻辑复杂的单个表达式,但假设我是解释器实现者,嵌套对象似乎是实现嵌套生成器表达式的最直接的方法

然而,在决定哪些表现更好时,还有其他因素在起作用。我了解到,最小化短期对象的创建是影响性能的一个重要因素,在Python中,这样做有时很难注意到

性能差:
(f(x)表示范围(100)内的x)#生成100个元素列表

更好的性能:
(f(x)表示x范围内的x(100))#使用计数迭代器

我在自己的实现中一直使用
itertools
模块中的
imap
ifilter
izip
,我发现它们性能良好。虽然每次调用它们都会创建一个新的迭代器对象,但这是相当轻量级的,有点像一个列表,其中从不包含多个项。此外,在CPython中,这些都是用C实现的,因此非常高效

在封面下,用纯Python实现的迭代器有一个
next
方法,调用该方法来检索每个数据。方法调用的成本不是很大,但也不是零。因此,如果您的代码将在一个必须尽可能优化的紧密循环中使用,下面是我的建议:

  • 在可能的情况下,一定要使用
    imap
    ifilter
    izip
    ,而不是
    map
    filter
    zip
    ,它们在内存中构建结果列表并返回结果。如果您有使用基于列表的版本的代码,那么通过更改为基于迭代器的版本,您将看到一个很大的改进
  • itertools
    模块包含其他函数,如
    takewhile
    starmap
    chain
    chain.from.\u iterable
    ,这些函数在链式迭代器实现中通常很有用
  • 与其链接
    ifilter
    的多个应用程序,不如尽可能组合传入的函数。例如,将过滤器组合为
    ifilter(lambda v:v>0,ifilter(lambda v:v%3==0,数据)
    ,而不是
    ifilter(lambda v:v>0)和(v%3==0,数据)
    。在某些情况下,重新排列操作顺序可能是有效的,这样您就可以按这种方式折叠它们
  • 当您为了实现副作用而执行映射操作,并且对结果不感兴趣时,可以使用此操作而不是
    map
    ,以避免结果在内存中累积:

    def consume(i):
      u'eat all contents of given iterator'
      while True:
        i.next()
    
    consume(imap(side_effect, data))
    
最后,要注意可能会增加内存使用率或重复创建和销毁不必要的对象的其他问题,这会给垃圾收集器带来压力。这实际上与迭代器无关,但会影响性能。下面的函数在内存中创建lambda表达式,并在每次调用时将其丢弃:

def foo(data):
  return reduce(R, imap(bar, ifilter(lambda v: v % 5 == 0, data)))
一种修复方法(此方法每次仍将创建两个迭代器对象,这是必需的,但不是附加的lambda表达式):


(注意:答案适用于Python2。在Python3中,
map
filter
zip
返回迭代器。)

根据官方文档,使用生成器表达式基本上等同于调用
imap
,因为它创建了迭代器。(“”)没有明确讨论嵌套表达式是创建单独(组合)的对象,还是创建内部逻辑复杂的单个表达式,但假设我是解释器实现者,嵌套对象似乎是实现嵌套生成器表达式的最直接的方法

然而,在决定哪些表现更好时,还有其他因素在起作用。我了解到,最小化短期对象的创建是影响性能的一个重要因素,在Python中,这样做有时很难注意到

性能差:
(f(x)表示范围(100)内的x)#生成100个元素列表

更好的性能:
(f(x)代表x范围内的x
_mod5zero = lambda v: v % 5 == 0
def foo(data):
  return reduce(R, imap(bar, ifilter(_mod5zero, data)))
def foo1():
    data = ((a,b) for a in (i*i for i in xrange(1,101) if i%2) for b in [1,2,3,4,5] if a+b > 40)
    return list(data)
def foo2():
    result=[]
    for i in range(1,101):
        if i%2:
            i=i*i
            for j in [1,2,3,4,5]:
                if i+j > 40:
                    result+=[(i,j)]
    return result
def foo3():
    data=[(a,b) for a in (i*i for i in range(1,101)) for b in [1,2,3,4,5] if a+b > 40] 
    return data
def foo4():
    data=[(a,b) for a in [i*i for i in range(1,101)] for b in [1,2,3,4,5] if a+b > 40]
    return data
>>> t1=timeit.Timer("foo1()","from __main__ import foo1")
>>> t2=timeit.Timer("foo2()","from __main__ import foo2")
>>> t3=timeit.Timer("foo3()","from __main__ import foo3")
>>> t4=timeit.Timer("foo4()","from __main__ import foo4")

>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=10000)/10000)
100.95 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=10000)/10000)
158.90 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t3.timeit(number=10000)/10000)
130.02 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t4.timeit(number=10000)/10000)
133.68 usec/pass
>>>