【问题标题】:Python any() + generator expressionPython any() + 生成器表达式
【发布时间】:2014-04-02 05:15:33
【问题描述】:

根据博文here,any() + 生成器表达式应该比 for 循环运行得更快,看来他的推理是有道理的。

但是我尝试过使用这种方法(尽管在其他一些函数上),但它的运行时间似乎比显式 for 循环要长。

def with_loop(a, b):
    for x in xrange(1, b):
        if x * a % b == 1: return True
    return False

def with_generator(a, b):
    return any(x * a % b == 1 for x in xrange(1, b))

基本上,代码循环遍历从 1 到 b 的所有数字,以查找数字 a 是否具有模逆。

我从运行这些函数中得到的时间是:

>>> from timeit import Timer as T
>>> T(lambda : with_generator(100, 300)).repeat(number = 100000)
[3.4041796334919923, 3.6303230626526215, 3.6714475531563266]
>>> T(lambda : with_loop(100, 300)).repeat(number = 100000)
[2.1977450660490376, 2.2083902291327604, 2.1905292602997406]
>>> T(lambda : with_generator(101, 300)).repeat(number = 100000)
[1.213779524696747, 1.2228346702509043, 1.2216941170634072]
>>> T(lambda : with_loop(101, 300)).repeat(number = 100000)
[0.7431202233722161, 0.7444361146951906, 0.7525384471628058]

with_generator(100,300) 返回 False,with_generator(101,300) 返回 True。

似乎 with_generator 的运行时间总是比 with_loop 长。这有什么原因吗?

编辑: 是否有任何其他更短或更优雅的方式来重写 with_loop 以便我们获得相似或更好的性能?谢谢!

【问题讨论】:

  • 进一步了解下面用户的回答,是否还有其他“更优雅”或更短的方式来编写上面的代码,但仍能达到与 with_loop 相似/更好的速度?

标签: performance python-2.7 for-loop generator any


【解决方案1】:

上下文

我认为

any() + 生成器表达式应该比 for 循环运行得更快

表示any 不会生成所有值,但循环会生成:

>>> T(lambda : any([x * 101 % 300 == 1 for x in xrange(1, 300)])).repeat(number = 100000)
[5.7612644951345935, 5.742304846931542, 5.746804810873488]
>>> T(lambda : any(x * 101 % 300 == 1 for x in xrange(1, 300))).repeat(number = 100000)
[2.1652204281427814, 2.1640463131248886, 2.164674290446399]

所以引用并不意味着循环永远无法达到生成器的性能。

引号表示循环通常会生成所有元素,而 any 不会使用所有元素,而生成器只会生成 any 使用的元素。

您的函数with_loop 相当于生成器。所以你不能指望不同的行为。

说得更清楚一点:any(loop)any(generator) 慢,因为循环会生成所有内容。你的with_loop 等同于any(generator) 而不是any(loop)

原始问题

>>> profile.run("""T(lambda : with_loop(101, 300)).repeat(number = 100000)""")
         600043 function calls (600040 primitive calls) in 6.133 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        3    0.000    0.000    0.000    0.000 :0(append)
        6    0.000    0.000    0.000    0.000 :0(clock)
        3    0.000    0.000    0.000    0.000 :0(disable)
        3    0.000    0.000    0.000    0.000 :0(enable)
        3    0.000    0.000    0.000    0.000 :0(globals)
        1    0.000    0.000    0.000    0.000 :0(hasattr)
        3    0.000    0.000    0.000    0.000 :0(isenabled)
        2    0.000    0.000    0.000    0.000 :0(isinstance)
        1    0.000    0.000    0.000    0.000 :0(range)
        1    0.005    0.005    0.005    0.005 :0(setprofile)
   300000    0.579    0.000    5.841    0.000 <string>:1(<lambda>)
      4/1    0.000    0.000    6.128    6.128 <string>:1(<module>)
   300000    5.262    0.000    5.262    0.000 <string>:1(with_loop)
        1    0.000    0.000    6.133    6.133 profile:0(T(lambda : with_loop(101, 300)).repeat(number = 100000))
        0    0.000             0.000          profile:0(profiler)
        1    0.000    0.000    0.000    0.000 timeit.py:121(__init__)
        3    0.000    0.000    0.000    0.000 timeit.py:143(setup)
        3    0.000    0.000    6.128    2.043 timeit.py:178(timeit)
        1    0.000    0.000    6.128    6.128 timeit.py:201(repeat)
        1    0.000    0.000    0.000    0.000 timeit.py:94(_template_func)
        3    0.287    0.096    6.128    2.043 timeit.py:96(inner)


>>> profile.run("""T(lambda : with_generator(101, 300)).repeat(number = 100000)""")
         31500043 function calls (31500040 primitive calls) in 70.531 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   300000   30.898    0.000   67.590    0.000 :0(any)
        3    0.000    0.000    0.000    0.000 :0(append)
        6    0.000    0.000    0.000    0.000 :0(clock)
        3    0.000    0.000    0.000    0.000 :0(disable)
        3    0.000    0.000    0.000    0.000 :0(enable)
        3    0.000    0.000    0.000    0.000 :0(globals)
        1    0.000    0.000    0.000    0.000 :0(hasattr)
        3    0.000    0.000    0.000    0.000 :0(isenabled)
        2    0.000    0.000    0.000    0.000 :0(isinstance)
        1    0.000    0.000    0.000    0.000 :0(range)
        1    0.000    0.000    0.000    0.000 :0(setprofile)
   300000    0.667    0.000   70.222    0.000 <string>:1(<lambda>)
      4/1    0.000    0.000   70.531   70.531 <string>:1(<module>)
   300000    1.629    0.000   69.555    0.000 <string>:6(with_generator)
 30600000   37.027    0.000   37.027    0.000 <string>:7(<genexpr>)
        1    0.000    0.000   70.531   70.531 profile:0(T(lambda : with_generator(101, 300)).repeat(number = 100000))
        0    0.000             0.000          profile:0(profiler)
        1    0.000    0.000    0.000    0.000 timeit.py:121(__init__)
        3    0.000    0.000    0.000    0.000 timeit.py:143(setup)
        3    0.000    0.000   70.531   23.510 timeit.py:178(timeit)
        1    0.000    0.000   70.531   70.531 timeit.py:201(repeat)
        1    0.000    0.000    0.000    0.000 timeit.py:94(_template_func)
        3    0.309    0.103   70.531   23.510 timeit.py:96(inner)

每次调用生成器,30600000 次,似乎比仅仅一个 for 循环要慢得多。

如果你知道一个列表中有多少个元素,那么你可以这样写:

l[0] * 101 % 300 == 1 or l[1] * 101 % 300 == 1 or l[2] * 101 % 300 == 1 or ....

【讨论】:

  • 这很有意义!我不知道 any() 会调用生成器这么多次!无论如何,是否有任何其他“更优雅”或更短的方式来编写 with_loop(使用相同的想法),例如 with_generator 是如何写在一行中的?当然,在保持/提高速度的同时。
  • 我认为 Python 中没有功能性的方法可以以不同的方式和同样快速地编写这个 for 循环。如果你知道元素的数量,你也可以使用...[0]... or ...[1]... or ...[2]... 但除此之外我认为没有办法。
  • 您能否详细说明 ...[0]... 部分?我真的不明白。谢谢!
  • 完成。它在答案中。我真的应该多写一点。
猜你喜欢
  • 2017-12-08
  • 2019-11-24
  • 2021-12-27
  • 1970-01-01
  • 2021-10-17
  • 2021-04-26
  • 2012-08-20
  • 2011-01-01
相关资源
最近更新 更多