在回答问题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
这又是一个感知问题,但对我来说LC
比starmap
更具可读性。
要使用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
答案 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)
一旦你开始需要引入lambda
或filter
,你应该切换到列表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%。