2012-05-04 60 views
6

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

对我来说,list comprehension语法看起来更清晰,更易读,更简洁且更具Pythonic。但仍然starmap在itertools中可用,我想知道,它必须有一个原因。

我的问题是当starmap可能优于List Comprehension

注意如果其风格的问题,然后它绝对违背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 
+0

我不认为按照您的方式比较它们是公平的。这两个函数都应该将差异保存为'deltas',因为目前''use_star_map'由于它全部在一行中而不易读。改为:'deltas = starmap(sub,zip(nums [1:],nums))'sum(deltas)/ float(len(nums)-1)' – jamylak

+0

@jamylak:谢谢你指出。但不幸的是,它不会改变性能差异。 – Abhijit

+0

这不是我们的意思,但我们也在谈论可读性。 – jamylak

回答

3

这在很大程度上是一种风格的东西。选择你发现更可读的。

关于“只有一个办法做到这一点”,斯文Marnach好心提供了这种Guido quote

“你可能会认为这违反了TOOWTDI,但正如我以前说过的,这 是白色谎言(以及对Perl的口号 2000的厚颜无耻回应)。能够表达意图(对人类阅读器)通常需要基本上做同样的事情, 但看起来不同读者多种形式之间 选择。”

在性能热点,你可能想选择解决方案运行速度最快(我猜在这种情况下应该是基于starmap的解决方案)。

关于性能 - 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出来一点点快,可能是因为它避免了荏苒/解构。

+0

但是,Python的禅说:“应该有一个 - 最好只有一个 - 明显的方法来做到这一点。” – Abhijit

+0

@Abhijit是的,但是你不应该相信你读的所有东西。 Python经常有几种同样完成给定任务的好方法。 – Marcin

+0

但是,这不是Python的核心哲学,它使它不同于'Ruby','Perl' .... – Abhijit

10

我通常看到的差异是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 /发电机的表达,但在情况下,它是一个单个函数,对于列表理解的生成器表达式,语法感觉非常冗长。

他们互换的,如有疑问,坚持发电机表达,因为它是一般更具可读性,但在使用map()/starmap()有时会令事情变得更容易阅读的简单情况(map(int, strings)starmap(Vector, points))。

实施例:

其中我认为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()

python -m timeit -s "from itertools import starmap" -s "numbers = zip(range(100000), range(100000))" "sum(starmap(lambda x, y: x-y, numbers))" 
1000000 loops, best of 3: 0.546 usec per loop 
+0

'map(f,y)'对于'[f(x)for x in y]'而不是'(f(x)for x in y)'是等价的,因为它不是一个生成器。它立即执行。 – akaRem

+1

@akaRem Lattyware总是使用python 3. – Marcin

+0

@akaRem对不起,我在说Python 3.x - 事实上,在2.x中,这是事实。更新澄清。 –

0

关于星图.. 比方说你有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)]) 

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

所以,星图在某些情况下是一个非常有用的工具。有了它,你可以编写更简单易懂的代码。

编辑增加了一些假的测试

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))") 

输出(蟒蛇2.7.2)

5.23479851154 
5.35265309689 
4.48601346328 

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

+0

我会争辩说,在你更复杂的情况下,'starmap()'和生成器表达式都不是一个好的解决方案。在这一点上,扩展你的代码(例如,成为一个完整的生成器)会更具可读性。 –

+0

“太多噪音”是语法错误还是故意? – jamylak

+0

英语不是我的母语..我的意思是有很多不同的字符造成视觉噪音。因此,即使对于简单的阅读,这些代码也很难(不需要理解)。 – akaRem