首页 文章

哪个是在Python中连接字符串的首选方法?

提问于
浏览
246

由于Python的 string 无法更改,我想知道如何更有效地连接字符串?

我可以这样写:

s += stringfromelsewhere

或者像这样:

s = []
s.append(somestring)

later

s = ''.join(s)

在写这个问题的时候,我发现了一篇很好的文章谈论这个话题 .

http://www.skymind.com/~ocrow/python_string/

但它在Python 2.x.中,所以问题是在Python 3中做了哪些改变?

11 回答

  • 6

    使用就地字符串连接是在稳定性和交叉实现方面的最糟糕的连接方法,因为它不支持所有值 . PEP8标准不鼓励这种做法,并鼓励使用format(),join()和append()进行长期使用 .

  • 1

    正如@jdi提到的Python文档建议使用 str.joinio.StringIO 进行字符串连接 . 并且说开发人员应该在循环中预期来自 += 的二次时间,即使自Python 2.4以来存在优化 . 正如this回答说:

    如果Python检测到左参数没有其他引用,则调用realloc以尝试通过调整字符串的大小来避免副本 . 这不是你应该依赖的东西,因为它是一个实现细节,因为如果realloc最终需要经常移动字符串,那么无论如何性能都会降低到O(n ^ 2) .

    我将展示一个天真依赖于此优化的真实代码示例,但它不适用 . 下面的代码将可迭代的短字符串转换为更大的块,以便在批量API中使用 .

    def test_concat_chunk(seq, split_by):
        result = ['']
        for item in seq:
            if len(result[-1]) + len(item) > split_by: 
                result.append('')
            result[-1] += item
        return result
    

    由于二次时间复杂度,此代码可以运行数小时 . 以下是建议数据结构的替代方案:

    import io
    
    def test_stringio_chunk(seq, split_by):
        def chunk():
            buf = io.StringIO()
            size = 0
            for item in seq:
                if size + len(item) <= split_by:
                    size += buf.write(item)
                else:
                    yield buf.getvalue()
                    buf = io.StringIO()
                    size = buf.write(item)
            if size:
                yield buf.getvalue()
    
        return list(chunk())
    
    def test_join_chunk(seq, split_by):
        def chunk():
            buf = []
            size = 0
            for item in seq:
                if size + len(item) <= split_by:
                    buf.append(item)
                    size += len(item)
                else:
                    yield ''.join(buf)                
                    buf.clear()
                    buf.append(item)
                    size = len(item)
            if size:
                yield ''.join(buf)
    
        return list(chunk())
    

    还有一个微基准:

    import timeit
    import random
    import string
    import matplotlib.pyplot as plt
    
    line = ''.join(random.choices(
        string.ascii_uppercase + string.digits, k=512)) + '\n'
    x = []
    y_concat = []
    y_stringio = []
    y_join = []
    n = 5
    for i in range(1, 11):
        x.append(i)
        seq = [line] * (20 * 2 ** 20 // len(line))
        chunk_size = i * 2 ** 20
        y_concat.append(
            timeit.timeit(lambda: test_concat_chunk(seq, chunk_size), number=n) / n)
        y_stringio.append(
            timeit.timeit(lambda: test_stringio_chunk(seq, chunk_size), number=n) / n)
        y_join.append(
            timeit.timeit(lambda: test_join_chunk(seq, chunk_size), number=n) / n)
    plt.plot(x, y_concat)
    plt.plot(x, y_stringio)
    plt.plot(x, y_join)
    plt.legend(['concat', 'stringio', 'join'], loc='upper left')
    plt.show()
    

    micro-benchmark

  • 5

    在Python> = 3.6中,新的f-string是连接字符串的有效方法 .

    >>> name = 'some_name'
    >>> number = 123
    >>>
    >>> f'Name is {name} and the number is {number}.'
    'Name is some_name and the number is 123.'
    
  • 2

    我的用例略有不同 . 我不得不构建一个查询,其中超过20个字段是动态的 . 我遵循这种使用格式方法的方法

    query = "insert into {0}({1},{2},{3}) values({4}, {5}, {6})"
    query.format('users','name','age','dna','suzan',1010,'nda')
    

    这对我来说相对简单,而不是使用或其他方式

  • 322

    虽然有点过时了,Code Like a Pythonista: Idiomatic Python建议 join() 超过 + in this section . 与string concatenationstring concatenation一节中一样,并附有以下免责声明:

    对于更高版本的Python,本节的准确性存在争议 . 在CPython 2.5中,字符串连接相当快,尽管这可能不适用于其他Python实现 . 有关讨论,请参见ConcatenationTestCode .

  • 6

    如果连接的字符串是文字,请使用String literal concatenation

    re.compile(
            "[A-Za-z_]"       # letter or underscore
            "[A-Za-z0-9_]*"   # letter, digit or underscore
        )
    

    如果要对字符串的一部分进行注释(如上所述),或者如果要对文字的一部分使用raw strings或三引号但不是全部,则此选项非常有用 .

    由于这发生在语法层,因此它使用零连接运算符 .

  • 36

    如果你要连接很多值,那么两者都没有 . 附加清单很昂贵 . 您可以使用StringIO . 特别是如果你在很多操作中构建它 .

    from cStringIO import StringIO
    # python3:  from io import StringIO
    
    buf = StringIO()
    
    buf.write('foo')
    buf.write('foo')
    buf.write('foo')
    
    buf.getvalue()
    # 'foofoofoo'
    

    如果您已经从其他操作返回了完整列表,那么只需使用 ''.join(aList)

    来自python FAQ:What is the most efficient way to concatenate many strings together?

    str和bytes对象是不可变的,因此将多个字符串连接在一起效率很低,因为每个连接都会创建一个新对象 . 在一般情况下,总运行时间成本是总字符串长度的二次方 . 为了累积许多str对象,推荐的习惯用法是将它们放入一个列表并在结尾处调用str.join():chunks = []
    for my_strings中的s:
    chunks.append(S)
    result ='' . join(chunk)
    (另一个合理有效的习惯用法是使用io.StringIO)为了累积许多字节对象,推荐的习惯用法是使用就地连接(=运算符)扩展一个bytearray对象:result = bytearray()
    对于my_bytes_objects中的b:
    结果= b


    编辑:我很傻,并将结果粘贴回来,使得看起来像是附加到列表比cStringIO更快 . 我还添加了对bytearray / str concat的测试,以及使用带有更大字符串的更大列表的第二轮测试 . (python 2.7.3)

    ipython test example for large lists of strings

    try:
        from cStringIO import StringIO
    except:
        from io import StringIO
    
    source = ['foo']*1000
    
    %%timeit buf = StringIO()
    for i in source:
        buf.write(i)
    final = buf.getvalue()
    # 1000 loops, best of 3: 1.27 ms per loop
    
    %%timeit out = []
    for i in source:
        out.append(i)
    final = ''.join(out)
    # 1000 loops, best of 3: 9.89 ms per loop
    
    %%timeit out = bytearray()
    for i in source:
        out += i
    # 10000 loops, best of 3: 98.5 µs per loop
    
    %%timeit out = ""
    for i in source:
        out += i
    # 10000 loops, best of 3: 161 µs per loop
    
    ## Repeat the tests with a larger list, containing
    ## strings that are bigger than the small string caching 
    ## done by the Python
    source = ['foo']*1000
    
    # cStringIO
    # 10 loops, best of 3: 19.2 ms per loop
    
    # list append and join
    # 100 loops, best of 3: 144 ms per loop
    
    # bytearray() +=
    # 100 loops, best of 3: 3.8 ms per loop
    
    # str() +=
    # 100 loops, best of 3: 5.11 ms per loop
    
  • 0

    你也可以使用它(效率更高) . (https://softwareengineering.stackexchange.com/questions/304445/why-is-s-better-than-for-concatenation

    s += "%s" %(stringfromelsewhere)
    
  • 3

    将字符串附加到字符串变量的最佳方法是使用 ++= . 这是因为它可读且快速 . 它们也同样快,你选择的是品味问题,后者是最常见的 . 以下是 timeit 模块的时间安排:

    a = a + b:
    0.11338996887207031
    a += b:
    0.11040496826171875
    

    但是,建议有列表并附加到这些列表然后加入这些列表的人这样做是因为在扩展字符串时,将字符串附加到列表可能非常快 . 在某些情况下,这可能是真的 . 例如,这里是一个字符串的一百万个附加,首先是一个字符串,然后是一个列表:

    a += b:
    0.10780501365661621
    a.append(b):
    0.1123361587524414
    

    好的,事实证明即使结果字符串长达一百万个字符,附加仍然更快 .

    现在让我们尝试附加一个千字长串十万次:

    a += b:
    0.41823482513427734
    a.append(b):
    0.010656118392944336
    

    因此,结束字符串最终长约100MB . 这很慢,附加到列表要快得多 . 那个时间不包括最后的 a.join() . 那需要多长时间?

    a.join(a):
    0.43739795684814453
    

    Oups . 即使在这种情况下,附加/加入也会变慢 .

    那么这个建议来自哪里? Python 2?

    a += b:
    0.165287017822
    a.append(b):
    0.0132720470428
    a.join(a):
    0.114929914474
    

    好吧,如果你使用的是非常长的字符串(在内存中你通常不是100MB,那么追加/加入的速度会稍微快一点吗?)

    但真正的关键是Python 2.3 . 我在哪里赢得了如此之慢以至于尚未完成 . 这些测试突然需要几分钟 . 除了追加/连接之外,它与后来的Pythons一样快 .

    对 . 在石器时代,Python中的字符串连接非常慢 . 但是在2.4它已经不再存在(或者至少是Python 2.4.7),所以使用append / join的建议在2008年过时了,当时Python 2.3停止更新,你应该已经停止使用它了 . :-)

    (更新:当我更仔细地进行测试时,使用 ++= 对于Python 2.3上的两个字符串来说也更快 . 建议使用 ''.join() 必须是一个误解)

    但是,这是CPython . 其他实现可能有其他问题 . 这也是为什么过早优化是万恶之源的另一个原因 . 除非你先测量它,否则不要使用“更快”的技术 .

    Therefore the "best" version to do string concatenation is to use + or += . 如果这对你来说变得缓慢,这是不太可能的,那么做一些别的事情 .

    那么为什么我在代码中使用了很多追加/加入?因为有时它实际上更清晰 . 特别是当你应该连接在一起时,应该用空格或逗号或换行符分隔 .

  • 2

    推荐的方法仍然是使用append和join .

  • 7

    你写这个功能

    def str_join(*args):
        return ''.join(map(str, args))
    

    然后,您可以随时随地拨打电话

    str_join('Pine')  # Returns : Pine
    str_join('Pine', 'apple')  # Returns : Pineapple
    str_join('Pine', 'apple', 3)  # Returns : Pineapple3
    

相关问题