python列表理解vs+=

python列表理解vs+=,python,performance,list-comprehension,augmented-assignment,Python,Performance,List Comprehension,Augmented Assignment,今天我试图找到一个方法,用python对字符串进行一些处理。据说一些比我更高级的程序员不使用+=,而是使用''。join()我也可以在例如中读到这一点。 但我自己测试了一下,发现了一个有点奇怪的结果(不是我试图猜测它们,而是我想了解它们)。 这个想法是,如果有一个字符串“这是一个包含空格的示例文本”,则该字符串应转换为包含空格的这是一个“示例文本”,空格将被删除,但仅在引号之外 我测量了我的算法的两个不同版本的性能,一个使用''.join(list),另一个使用+= import time #

今天我试图找到一个方法,用python对字符串进行一些处理。据说一些比我更高级的程序员不使用
+=
,而是使用
''。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
之间的性能差异取决于许多因素:

  • 操作系统。在类unix或Windows系统上对越来越大的字符串运行此命令可能会产生完全不同的结果。通常,在Windows下,您会看到运行时的发音增加很多

  • Python实现。默认情况下,我们讨论的是CPython,但也有其他实现,如Jython或PyPy。让我们看看派比。使用上面答案中的源代码:

    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与CPython相比以其速度提升而闻名,
    +=
    版本的速度较慢。这是一个深思熟虑的决定,不包括在内 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