python列表理解vs+=
今天我试图找到一个方法,用python对字符串进行一些处理。据说一些比我更高级的程序员不使用python列表理解vs+=,python,performance,list-comprehension,augmented-assignment,Python,Performance,List Comprehension,Augmented Assignment,今天我试图找到一个方法,用python对字符串进行一些处理。据说一些比我更高级的程序员不使用+=,而是使用''。join()我也可以在例如中读到这一点。 但我自己测试了一下,发现了一个有点奇怪的结果(不是我试图猜测它们,而是我想了解它们)。 这个想法是,如果有一个字符串“这是一个包含空格的示例文本”,则该字符串应转换为包含空格的这是一个“示例文本”,空格将被删除,但仅在引号之外 我测量了我的算法的两个不同版本的性能,一个使用''.join(list),另一个使用+= import time #
+=
,而是使用''。join()
我也可以在例如中读到这一点。
但我自己测试了一下,发现了一个有点奇怪的结果(不是我试图猜测它们,而是我想了解它们)。
这个想法是,如果有一个字符串“这是一个包含空格的示例文本”
,则该字符串应转换为包含空格的这是一个“示例文本”
,空格将被删除,但仅在引号之外
我测量了我的算法的两个不同版本的性能,一个使用''.join(list)
,另一个使用+=
import time
#uses '+=' operator
def strip_spaces ( s ):
ret_val = ""
quote_found = False
for i in s:
if i == '"':
quote_found = not quote_found
if i == ' ' and quote_found == True:
ret_val += i
if i != ' ':
ret_val += i
return ret_val
#uses "".join ()
def strip_spaces_join ( s ):
#ret_val = ""
ret_val = []
quote_found = False
for i in s:
if i == '"':
quote_found = not quote_found
if i == ' ' and quote_found == True:
#ret_val = ''.join( (ret_val, i) )
ret_val.append(i)
if i != ' ':
#ret_val = ''.join( (ret_val,i) )
ret_val.append(i)
return ''.join(ret_val)
def time_function ( function, data):
time1 = time.time();
function(data)
time2 = time.time()
print "it took about {0} seconds".format(time2-time1)
print '#using += yields ', timeit.timeit('f(string)', 'from __main__ import string, strip_spaces as f', number=1000)
print '#using \'\'.join() yields ', timeit.timeit('f(string)', 'from __main__ import string, strip_spaces_join as f', number=1000)
在我的机器上,使用+=
import time
#uses '+=' operator
def strip_spaces ( s ):
ret_val = ""
quote_found = False
for i in s:
if i == '"':
quote_found = not quote_found
if i == ' ' and quote_found == True:
ret_val += i
if i != ' ':
ret_val += i
return ret_val
#uses "".join ()
def strip_spaces_join ( s ):
#ret_val = ""
ret_val = []
quote_found = False
for i in s:
if i == '"':
quote_found = not quote_found
if i == ' ' and quote_found == True:
#ret_val = ''.join( (ret_val, i) )
ret_val.append(i)
if i != ' ':
#ret_val = ''.join( (ret_val,i) )
ret_val.append(i)
return ''.join(ret_val)
def time_function ( function, data):
time1 = time.time();
function(data)
time2 = time.time()
print "it took about {0} seconds".format(time2-time1)
print '#using += yields ', timeit.timeit('f(string)', 'from __main__ import string, strip_spaces as f', number=1000)
print '#using \'\'.join() yields ', timeit.timeit('f(string)', 'from __main__ import string, strip_spaces_join as f', number=1000)
使用timeit计时时:
#using += yields 0.0130770206451
#using ''.join() yields 0.0108470916748
差别真的很小。但是为什么'.join()
没有明确地执行使用+=
的函数,但是对于'.join()版本来说似乎有一点优势。
我用python-2.7.3在Ubuntu12.04上测试了这一点。在比较算法时,请使用正确的方法;使用可消除CPU利用率和交换的波动 使用
timeit
可以看出这两种方法之间的差别很小,但是''。join()
稍微快一点:
函数中的大部分工作是循环每个字符并测试引号和空格,而不是字符串连接本身。此外,'.join()
变量做了更多的工作;首先将元素附加到列表中(这将替换+=
字符串连接操作),然后在末尾使用'.join()
连接这些值。而这种方法的速度仍然稍快
您可能希望剥离正在进行的工作,以便仅比较连接部分:
def inplace_add_concatenation(s):
res = ''
for c in s:
res += c
def str_join_concatenation(s):
''.join(s)
这表明:
>>> s = list(1000 * string)
>>> timeit.timeit('f(s)', 'from __main__ import s, inplace_add_concatenation as f', number=1000)
6.113742113113403
>>> timeit.timeit('f(s)', 'from __main__ import s, str_join_concatenation as f', number=1000)
0.6616439819335938
这显示了
'.join()
连接仍然比+=
快得多。速度差在回路中s
在这两种情况下都是一个列表,但是'.join()
在C中循环值,而另一个版本则必须在Python中循环。另一个选择是编写一个函数,使用生成器进行连接,而不是每次都附加到列表中
例如:
def strip_spaces_gen(s):
quote_found = False
for i in s:
if i == '"':
quote_found = not quote_found
if i == ' ' and quote_found == True:
# Note: you (c|sh)ould drop the == True, but I'll leave it here so as to not give an unfair advantage over the other functions
yield i
if i != ' ':
yield i
def strip_spaces_join_gen(ing):
return ''.join(strip_spaces_gen(ing))
对于较短的字符串,这似乎与连接大致相同:
In [20]: s = "This is \"an example text\" containing spaces"
In [21]: %timeit strip_spaces_join_gen(s)
10000 loops, best of 3: 22 us per loop
In [22]: %timeit strip_spaces(s)
100000 loops, best of 3: 13.8 us per loop
In [23]: %timeit strip_spaces_join(s)
10000 loops, best of 3: 23.1 us per loop
但对于较大的字符串,速度更快
In [24]: s = s * 1000
In [25]: %timeit strip_spaces_join_gen(s)
100 loops, best of 3: 12.9 ms per loop
In [26]: %timeit strip_spaces(s)
100 loops, best of 3: 17.1 ms per loop
In [27]: %timeit strip_spaces_join(s)
100 loops, best of 3: 17.5 ms per loop
+=
和.join
之间的性能差异取决于许多因素:
CPython 2.7:
python concat.py
inplace_add_concatenation: 0.420897960663
str_join_concatenation: 0.061793088913
ratio: 6.81140833169
PyPy 1.9:
pypy concat.py
inplace_add_concatenation: 1.26573014259
str_join_concatenation: 0.0392870903015
ratio: 32.2174570038
+=
版本的速度较慢。这是一个深思熟虑的决定,不包括在内
PyPy的默认版本中的“+=”优化
处理绩效的经验法则:“永远不要猜测,永远要衡量。”
阅读文档也有助于:
6 CPython实现细节:如果s和t都是字符串,那么
诸如CPython之类的Python实现通常可以就地执行
s=s+t或s+=t形式赋值的优化。什么时候
适用时,这种优化使二次运行时间大大减少
很可能。这种优化既是版本优化也是实现优化
依赖的。对于性能敏感的代码,最好使用
确保一致线性连接的str.join()方法
跨版本和实现的性能。”“”
从
(这可能是OP已经知道的很多细节,但全面了解这个问题可能会帮助其他人解决这个问题)
mystring+=suffix
中的问题是字符串是不可变的,因此这实际上相当于mystring=mystring+suffix
。因此,实现必须创建一个新的字符串对象,将mystring
中的所有字符复制到该对象,然后从suffix
中复制所有字符。然后将反弹mystring
名称以引用新字符串;未触及mystring
引用的原始字符串对象
就其本身而言,这实际上不是问题。任何连接这两个字符串的方法都必须这样做,包括'.join([mystring,suffix])
;这实际上更糟,因为它必须首先构造一个列表对象,然后对其进行迭代,而在mystring
和suffix
之间拼接空字符串时没有实际的数据传输,这将至少需要一条指令来进行排序
当你反复做的时候,+=
就成了问题。类似这样:
mystring = ''
for c in 'abcdefg' * 1000000:
mystring += c
请记住,mystring+=c
相当于mystring=mystring+c
。因此在循环的第一次迭代中,它计算'+'a'
总共复制1个字符。接下来它执行'a'+'b'
总共复制2个字符。然后'ab'+'c'
3个字符,然后'abc'+'d'
4个字符,一个字符d我想你可以看到这是怎么回事。每个后续的+=
都在重复上一个的所有工作,然后复制新字符串。这会非常浪费
'.join(…)
更好,因为在那里,您可以等到知道所有字符串后再复制其中任何一个,然后将每个字符串直接复制到最终字符串对象中的正确位置。
mystring = ''
for c in 'abcdefg' * 1000000:
mystring += c
import timeit
def plus_equals(data):
s = ''
for c in data:
s += c
def simple_join(data):
s = ''.join(data)
def append_join(data):
l = []
for c in data:
l.append(c)
s = ''.join(l)
def plus_equals_non_garbage(data):
s = ''
for c in data:
dummy = s
s += c
def plus_equals_maybe_non_garbage(data):
s = ''
for i, c in enumerate(data):
if i % 1000 == 0:
dummy = s
s += c
def plus_equals_enumerate(data):
s = ''
for i, c in enumerate(data):
if i % 1000 == -1:
dummy = s
s += c
data = ['abcdefg'] * 1000000
for f in (
plus_equals,
simple_join,
append_join,
plus_equals_non_garbage,
plus_equals_maybe_non_garbage,
plus_equals_enumerate,
):
print '{:30}{:20.15f}'.format(f.__name__, timeit.timeit(
'm.{0.__name__}(m.data)'.format(f),
setup='import __main__ as m',
number=1
))
plus_equals 0.066924095153809
simple_join 0.013648986816406
append_join 0.086287975311279
plus_equals_non_garbage 540.663727998733521
plus_equals_maybe_non_garbage 0.731688976287842
plus_equals_enumerate 0.156824111938477