当前位置: 首页 > 面试题库 >

列表理解与生成器表达式的奇怪时间结果?

祝允晨
2023-03-14
问题内容

我正在回答这个问题,在这里我更喜欢生成器表达式并使用了它,我认为这样做会更快,因为生成器不需要先创建整个列表:

>>> lis=[['a','b','c'],['d','e','f']]
>>> 'd' in (y for x in lis for y in x)
True

Levon在解决方案中使用了列表理解功能,

>>> lis = [['a','b','c'],['d','e','f']]
>>> 'd' in [j for i in mylist for j in i]
True

但是当我做这些LC的时间结果比生成器快时:

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f']]" "'d' in (y for x in lis for y in x)"
    100000 loops, best of 3: 2.36 usec per loop
~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f']]" "'d' in [y for x in lis for y in x]"
    100000 loops, best of 3: 1.51 usec per loop

然后我增加了列表的大小,并再次计时:

lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]

这次搜索'd'生成器的时间比LC快,但是当我搜索中间元素(11)和最后一个元素时,LC再次击败了生成器表达式,我不明白为什么?

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "'d' in (y for x in lis for y in x)"
    100000 loops, best of 3: 2.96 usec per loop

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "'d' in [y for x in lis for y in x]"
    100000 loops, best of 3: 7.4 usec per loop

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "11 in [y for x in lis for y in x]"
100000 loops, best of 3: 5.61 usec per loop

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "11 in (y for x in lis for y in x)"
100000 loops, best of 3: 9.76 usec per loop

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "18 in (y for x in lis for y in x)"
100000 loops, best of 3: 8.94 usec per loop

~$ python -m timeit -s "lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]" "18 in [y for x in lis for y in x]"
100000 loops, best of 3: 7.13 usec per loop

问题答案:

扩展Paulo的答案,由于函数调用的开销,生成器表达式通常比列表理解要慢。在这种情况下,in如果发现的时间很早,则抵消了这种缓慢现象的短路行为,但否则,该模式仍然成立。

我通过探查器运行了一个简单的脚本,以进行更详细的分析。这是脚本:

lis=[['a','b','c'],['d','e','f'],[1,2,3],[4,5,6],
     [7,8,9],[10,11,12],[13,14,15],[16,17,18]]

def ge_d():
    return 'd' in (y for x in lis for y in x)
def lc_d():
    return 'd' in [y for x in lis for y in x]

def ge_11():
    return 11 in (y for x in lis for y in x)
def lc_11():
    return 11 in [y for x in lis for y in x]

def ge_18():
    return 18 in (y for x in lis for y in x)
def lc_18():
    return 18 in [y for x in lis for y in x]

for i in xrange(100000):
    ge_d()
    lc_d()
    ge_11()
    lc_11()
    ge_18()
    lc_18()

以下是相关结果,已重新排序以使模式更清晰。

         5400002 function calls in 2.830 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   100000    0.158    0.000    0.251    0.000 fop.py:3(ge_d)
   500000    0.092    0.000    0.092    0.000 fop.py:4(<genexpr>)
   100000    0.285    0.000    0.285    0.000 fop.py:5(lc_d)

   100000    0.356    0.000    0.634    0.000 fop.py:8(ge_11)
  1800000    0.278    0.000    0.278    0.000 fop.py:9(<genexpr>)
   100000    0.333    0.000    0.333    0.000 fop.py:10(lc_11)

   100000    0.435    0.000    0.806    0.000 fop.py:13(ge_18)
  2500000    0.371    0.000    0.371    0.000 fop.py:14(<genexpr>)
   100000    0.344    0.000    0.344    0.000 fop.py:15(lc_18)

创建生成器表达式等效于创建生成器函数并对其进行调用。占一通电话<genexpr>。然后,在第一种情况下,next被调用4次,直到d达到为止,总共5次调用(时间100000次迭代=
ncalls = 500000次)。在第二种情况下,它被调用了17次,总共有18次调用。第三次,共24次,总共25次通话。

在第一种情况下,genex的性能优于列表理解,但是next在第二和第三种情况下,额外的调用要考虑列表理解的速度与生成器表达式的速度之间的大部分差异。

>>> .634 - .278 - .333
0.023
>>> .806 - .371 - .344
0.091

我不确定剩下的时间是什么?似乎即使没有附加的函数调用,生成器表达式也要慢一些。我想这证实了inspectorG4dget的断言:“创建生成器理解要比列表理解具有更多的本机开销。”
但是无论如何,这很清楚地表明,生成器表达式的执行速度较慢, 主要是 因为对的调用next

我还要补充一点,当短路无济于事时,即使对于非常大的列表,列表理解 仍然会 更快。例如:

>>> counter = itertools.count()
>>> lol = [[counter.next(), counter.next(), counter.next()] 
           for _ in range(1000000)]
>>> 2999999 in (i for sublist in lol for i in sublist)
True
>>> 3000000 in (i for sublist in lol for i in sublist)
False
>>> %timeit 2999999 in [i for sublist in lol for i in sublist]
1 loops, best of 3: 312 ms per loop
>>> %timeit 2999999 in (i for sublist in lol for i in sublist)
1 loops, best of 3: 351 ms per loop
>>> %timeit any([2999999 in sublist for sublist in lol])
10 loops, best of 3: 161 ms per loop
>>> %timeit any(2999999 in sublist for sublist in lol)
10 loops, best of 3: 163 ms per loop
>>> %timeit for i in [2999999 in sublist for sublist in lol]: pass
1 loops, best of 3: 171 ms per loop
>>> %timeit for i in (2999999 in sublist for sublist in lol): pass
1 loops, best of 3: 183 ms per loop

如您所见,当短路无关紧要时,即使对于一百万个项目长的列表,列表理解也 始终
更快。显然,对于in这些规模的实际使用,由于短路,发电机将更快。但是对于在项目数量上真正线性的其他种类的迭代任务,列表理解 总是
快得多。如果您需要在一个列表上执行多个测试,则尤其如此。您可以 非常快速地 遍历已构建的列表理解:

>>> incache = [2999999 in sublist for sublist in lol]
>>> get_list = lambda: incache
>>> get_gen = lambda: (2999999 in sublist for sublist in lol)
>>> %timeit for i in get_list(): pass
100 loops, best of 3: 18.6 ms per loop
>>> %timeit for i in get_gen(): pass
1 loops, best of 3: 187 ms per loop

在这种情况下,列表理解要快一个数量级!

当然,只有在内存用完之前,情况才会如此。这把我带到了最后一点。使用发生器的主要原因有两个:利用短路的优势并节省内存。对于非常大的序列/可迭代对象,生成器是显而易见的方法,因为它们可以节省内存。但是,如果不能选择短路,那么您几乎
永远不会速度 列表上选择发电机。您选择它们​​来节省内存,这始终是一个权衡。



 类似资料:
  • 问题内容: 什么时候应该使用生成器表达式,什么时候应该在中使用列表推导? 问题答案: John的答案很好(当你要迭代多次时,列表理解会更好)。但是,还应注意,如果要使用任何列表方法,都应使用列表。例如,以下代码将不起作用: 基本上,如果你要做的只是迭代一次,则使用生成器表达式。如果你要存储和使用生成的结果,那么列表理解可能会更好。 由于性能是选择彼此的最常见原因,所以我的建议是不要担心它,而只选择

  • 本文向大家介绍python生成器表达式和列表解析,包括了python生成器表达式和列表解析的使用技巧和注意事项,需要的朋友参考一下 绝大多数情况下,遍历一个集合都是为了对元素应用某个动作或是进行筛选。如果看过本文的第二部分,你应该还记得有内建函数map和filter提供了这些功能,但Python仍然为这些操作提供了语言级的支持。 如你所见,生成器表达式和列表解析(注:这里的翻译有很多种,比如列表展

  • 本文向大家介绍python列表生成式与列表生成器的使用,包括了python列表生成式与列表生成器的使用的使用技巧和注意事项,需要的朋友参考一下 列表生成式:会将所有的结果全部计算出来,把结果存放到内存中,如果列表中数据比较多,就会占用过多的内存空间,可能会导致MemoryError内存错误或者导致程序在运行时出现卡顿的情况 列表生成器:会创建一个列表生成器对象,不会一次性的把所有结果都计算出来,如

  • 本文向大家介绍python 中的列表生成式、生成器表达式、模块导入,包括了python 中的列表生成式、生成器表达式、模块导入的使用技巧和注意事项,需要的朋友参考一下 5.16 列表生成式 5.17 列表生成式与生成器表达式的应用 第六章模块 什么是模块? 模块就是一系统功能的集合体,在python中,一个py文件就是一个模块,比如module.py,其中模块名module 6.1 import

  • 问题内容: 在Python中,通过 生成器表达式 创建生成器对象与使用 yield 语句之间有什么区别吗? 使用 yield : 使用 生成器表达式 : 这两个函数都返回生成器对象,这些对象生成元组,例如(0,0),(0,1)等。 一个或另一个有什么优势吗?有什么想法吗? 谢谢大家!这些答案中有很多不错的信息和进一步的参考! 问题答案: 两者之间只有细微的差别。您可以使用该模块自己检查这种事情。

  • 本文向大家介绍详解python列表生成式和列表生成式器区别,包括了详解python列表生成式和列表生成式器区别的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了python(列表生成式/器)的具体代码,供大家参考,具体内容如下 一、列表生成式 二、小例子 三、字典生成式 四、列表生成器和列表生成式的区别 列表生成式: 会将所有的结果全部计算出来,把结果存放到内存中,如果列表中数据比较多