当`starmap`可能优于`List Comprehension`

时间:2012-05-04 12:02:50

标签: python list-comprehension itertools

在回答问题Clunky calculation of differences between an incrementing set of numbers, is there a more beautiful way?时,我提出了两个解决方案,一个使用List Comprehension,另一个使用itertools.starmap

对我而言,list comprehension语法看起来更清晰,可读,更简洁,更具Pythonic。但是,当starmap在itertools中很好用时,我想知道,它必须有一个原因。

我的问题是starmap可能比List Comprehension优先考虑?

注意如果它是Style的问题那么它肯定与There should be one-- and preferably only one --obvious way to do it.

相矛盾

头对头比较

可读性计数。 --- LC

这又是一个感知问题,但对我来说LCstarmap更具可读性。 要使用starmap,您需要导入operator,或定义lambda或一些明确的multi-variable功能,然后从itertools额外导入。

效果 --- LC

>>> def using_star_map(nums):
    delta=starmap(sub,izip(nums[1:],nums))
    return sum(delta)/float(len(nums)-1)
>>> def using_LC(nums):
    delta=(x-y for x,y in izip(nums[1:],nums))
    return sum(delta)/float(len(nums)-1)
>>> nums=[random.randint(1,10) for _ in range(100000)]
>>> t1=Timer(stmt='using_star_map(nums)',setup='from __main__ import nums,using_star_map;from itertools import starmap,izip')
>>> t2=Timer(stmt='using_LC(nums)',setup='from __main__ import nums,using_LC;from itertools import izip')
>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=1000)/100000)
235.03 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=1000)/100000)
181.87 usec/pass

3 个答案:

答案 0 :(得分:12)

我通常看到的差异是map() / starmap()最适合你只是在列表中的每个项目上调用一个函数。在这种情况下,它们更清晰一点:

(f(x) for x in y)
map(f, y) # itertools.imap(f, y) in 2.x

(f(*x) for x in y)
starmap(f, y)

一旦你开始需要引入lambdafilter,你应该切换到列表comp / generator表达式,但是如果它是单个函数,对于列表推导的生成器表达式,语法感觉非常冗长。

他们 是可以互换的,如果有疑问,请坚持使用生成器表达式,因为它通常更具可读性,但在一个简单的情况下(map(int, strings),{{1 }})使用starmap(Vector, points) / map()有时可以让事情更容易阅读。

实施例

我认为starmap()更具可读性的示例:

starmap()

对于from collections import namedtuple from itertools import starmap points = [(10, 20), (20, 10), (0, 0), (20, 20)] Vector = namedtuple("Vector", ["x", "y"]) for vector in (Vector(*point) for point in points): ... for vector in starmap(Vector, points): ...

map()

性能:

values = ["10", "20", "0"]

for number in (int(x) for x in values):
    ...

for number in map(int, values):
    ...

用于构建python -m timeit -s "from itertools import starmap" -s "from operator import sub" -s "numbers = zip(range(100000), range(100000))" "sum(starmap(sub, numbers))" 1000000 loops, best of 3: 0.258 usec per loop python -m timeit -s "numbers = zip(range(100000), range(100000))" "sum(x-y for x, y in numbers)" 1000000 loops, best of 3: 0.446 usec per loop

namedtuple

在我的测试中,我们讨论的是使用简单函数(无python -m timeit -s "from itertools import starmap" -s "from collections import namedtuple" -s "numbers = zip(range(100000), reversed(range(100000)))" -s "Vector = namedtuple('Vector', ['x', 'y'])" "list(starmap(Vector, numbers))" 1000000 loops, best of 3: 0.98 usec per loop python -m timeit -s "from collections import namedtuple" -s "numbers = zip(range(100000), reversed(range(100000)))" -s "Vector = namedtuple('Vector', ['x', 'y'])" "[Vector(*pos) for pos in numbers]" 1000000 loops, best of 3: 0.375 usec per loop ),lambda比等效生成器表达式更快。当然,性能应该落后于可读性,除非它是一个经过验证的瓶颈。

starmap()如何杀死任何效果增益的示例,与第一组相同,但使用lambda代替lambda

operator.sub()

答案 1 :(得分:3)

这在很大程度上是一种风格。选择你认为更具可读性的那些。

关于“只有一种方法可以做到”,Sven Marnach友好地提供了这个Guido quote

  

“你可能认为这违反了TOOWTDI,但正如我之前所说的那样   是一个白色的谎言(以及对Perl的口号周围的厚颜无耻的回应   2000)。能够表达意图(对人类读者)通常需要   选择多种基本相同的形式,   但看起来与读者不同。“

在性能热点中,您可能希望选择运行速度最快的解决方案(我猜在这种情况下会基于starmap)。

关于表现 - 星图因其解构而变慢;但是这里不需要星图:

from timeit import Timer
import random
from itertools import starmap, izip,imap
from operator import sub

def using_imap(nums):
    delta=imap(sub,nums[1:],nums[:-1])
    return sum(delta)/float(len(nums)-1)

def using_LC(nums):
    delta=(x-y for x,y in izip(nums[1:],nums))
    return sum(delta)/float(len(nums)-1)

nums=[random.randint(1,10) for _ in range(100000)]
t1=Timer(stmt='using_imap(nums)',setup='from __main__ import nums,using_imap')
t2=Timer(stmt='using_LC(nums)',setup='from __main__ import nums,using_LC')

在我的电脑上:

>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=1000)/100000)
172.86 usec/pass
>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=1000)/100000)
178.62 usec/pass

imap出来的速度要快一点,可能是因为它避免了压缩/解构。

答案 2 :(得分:1)

关于Starmap .. 假设你有L = [(0,1,2),(3,4,5),(6,7,8),..]

生成器comprehansion看起来像

(f(a,b,c) for a,b,c in L)

(f(*item) for item in L) 

星图看起来像

starmap(f, L)

第三种变体更轻更短。但第一个是非常明显的,并没有强迫我做它做什么。

确定。现在我想写更复杂的内联代码..

some_result = starmap(f_res, [starmap(f1,L1), starmap(f2,L2), starmap(f3,L3)])

这条线不明显,但仍然易于理解.. 在生成器comprehansion中它看起来像:

some_result = (f_res(a,b,c) for a,b,c in [(f1(a,b,c) for a,b,c in L1), (f2(a,b,c) for a,b,c in L2), (f3(a,b,c) for a,b,c in L3)])

如你所见,它很长,很难理解,不能放在一行,因为它大于79个字符(PEP 8)。更短的变体是坏的:

some_result = (f_res(*item) for item [(f1(*item) for item in L1), (f(*item2) for item in L2), (f3(*item) for item in L3)])

字符太多..括号太多..噪音太大。

<强>因此。在某些情况下,Starmap是一个非常有用的工具。有了它,您可以编写更少的代码,更容易理解。

编辑添加了一些虚拟测试

from timeit import timeit
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list((max(a,b,c)for a,b,c in L))")
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list((max(*item)for item in L))")
print timeit("from itertools import starmap\nL = [(0,1,2),(3,4,5),(6,7,8)]\nt=list(starmap(max,L))")

输出(python 2.7.2)

5.23479851154
5.35265309689
4.48601346328

因此,星图在这里的速度甚至快了〜15%。