考虑这两个执行以下操作的函数:给定一个单词,它会生成一个列表,其中单词中的每个位置都被字母表中的每个字符替换
alphabet = 'abcdefghijklmnopqrstuvwxyz'
版本1(Pythonic):
def replace1_word( word ):
return [word[:w]+alphabet[i]+word[w+1:]
for w in range(len(word))
for i in range(len(alphabet))]
第2版(Unpythonic):
def replace1_word2( word ):
res=[]
for w in range(len(word)):
for i in range(len(alphabet)):
res.append( word[:w] + alphabet[i] + word[w+1:] )
return res
我使用timeit
模块运行1000次并测量时间,平均运行时间差异在0.028
毫秒到0.040
毫秒之间。
我的问题是第二版中代码的哪一部分/哪一行代价高昂?为什么?它们“似乎”以相同的方式工作,并以列表格式返回相同的结果。
答案 0 :(得分:6)
我的问题是代码的哪一部分/哪一行在第二部分是代价高昂的 版本和为什么?他们“似乎”以同样的方式工作并返回 列表格式相同的结果。
不,他们不是。如果有疑问,请始终对其进行分析,它将为您提供每个操作的成本图。只是看下面的O / P它现在告诉你,第二个功能的成本是多少?
>>> cProfile.run("replace1_word2('foo bar baz')")
313 function calls in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <pyshell#216>:1(replace1_word2)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
12 0.000 0.000 0.000 0.000 {len}
286 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
12 0.000 0.000 0.000 0.000 {range}
>>> cProfile.run("replace1_word('foo bar baz')")
27 function calls in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 <pyshell#220>:1(replace1_word)
1 0.000 0.000 0.000 0.000 <string>:1(<module>)
12 0.000 0.000 0.000 0.000 {len}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
12 0.000 0.000 0.000 0.000 {range}
注意解释什么替换附加(或版本1如何 生成列表)
在Python中,函数调用会产生额外的开销。在前一种情况下,list.append
被多次调用,而在列表理解的情况下,列表将作为单个表达式生成。所以从某种意义上说,对于具有循环结构的列表理解没有等效的符号。 List comprehension是Python中的一个强大工具,而不是循环的装饰语法。
<强>后记强>
如果你让我写一个函数来解决这个问题,我会以
结束>>> from itertools import product
>>> from string import ascii_lowercase
>>> def replace1_word4(word):
words = ('{}'.join([word[:w], word[w+1:]]) for w in range(len(word)))
return [word.format(replace)
for word, replace in product(words, ascii_lowercase)]
答案 1 :(得分:2)
它可能与列表推导的评估方式有关,而对于带有list.append
的循环,它与收集器变量有关;如果您更改第二个代码段以使用yield
,然后使用list()
包装其结果,则性能会更接近第一个版本:
def replace1_word3(word):
for w in range(len(word)):
for i in range(len(alphabet)):
yield word[:w] + alphabet[i] + word[w+1:]
基准:
In [18]: timeit replace1_word('foo bar baz ' * 100)
10 loops, best of 3: 38.7 ms per loop
In [19]: timeit replace1_word2('foo bar baz ' * 100)
10 loops, best of 3: 42.1 ms per loop
In [20]: timeit list(replace1_word3('foo bar baz ' * 100))
10 loops, best of 3: 39.7 ms per loop
剩下的差异可能归结于如何在列表理解中内部构建实际列表与yield
=&gt;的性能。 generator
=&gt; list()
。
P.S。 Abhijit的答案可以用更多技术术语解释为什么replace1_word
更快。无论如何,看起来list.append
是我猜测的罪魁祸首。
答案 2 :(得分:1)
谁在乎哪些被认为是“pythonic”而哪些不是? Python的主要设计目标和“pythonicness” - 关于易于阅读,不是,因为你的问题意味着使用timeit模块,关于特别高效的。
在你的特殊情况下,我认为第二个例子更具可读性,但只需迭代字母表而不是遍历其索引就可以使它更具可读性:
def replace1_word2( word ):
res=[]
for w in range(len(word)):
for letter in alphabet:
res.append( word[:w] + letter + word[w+1:] )
return res
此外,您可能不需要额外创建列表,yield
关键字也可以:
def replace1_word2( word ):
for w in range(len(word)):
for letter in alphabet:
yield word[:w] + letter + word[w+1:]
最后,虽然没有正式的指导方针,但很多人都在关注PEP8 styleguide。这可能是最有助于提高可读性和代码“pythonicness”的东西。在您的代码中,除了函数签名中的额外空格外,没有真正违反该样式指南的内容:
def replace1_word2( word ): # no
def replace1_word2(word): # yes
答案 3 :(得分:1)
也许更好的是这样的:
def replace1_word3( word ):
return [word[:w]+alphabet[i]+word[w+1:]
for w,i in product(xrange(len(word)), xrange(len(alphabet)))]
但它并不比第一个版本快,因为它实际上做了同样的事情。
略有改进:
def replace1_word4( word ):
return [word[:w]+i+word[w+1:]
for w,i in product(xrange(len(word)), alphabet)]
这有点不那么罗嗦 - 对于字母表,你不需要得到范围,然后是尊重;你可以直接使用这些值。但是,您可以在原始代码中进行相同的简化,并且可能获得相同的加速(单词是'pizzazzle':只有长度非常重要):
In [357]: %timeit replace1_word(word)
10000 loops, best of 3: 71.7 us per loop
In [358]: %timeit replace1_word2(word)
10000 loops, best of 3: 82.9 us per loop
In [359]: %timeit replace1_word3(word)
10000 loops, best of 3: 72.2 us per loop
In [360]: %timeit replace1_word4(word)
10000 loops, best of 3: 63.7 us per loop
答案 4 :(得分:0)
python
中的循环可能很慢。由于for循环的额外解释器开销,嵌套循环版本是两者中较慢的一个。
https://wiki.python.org/moin/PythonSpeed/PerformanceTips
答案 5 :(得分:0)
我认为其中任何一个都不是Pythonic。好多了:
def replace1_word2( word ):
res=[]
for w, letter in enumerate(word):
for alpha in alphabet:
res.append(word[:w] + alpha + word[w+1:])
return res