Какой предпочтительный способ объединения строки в Python?

поскольку в 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 ответов


на лучшие способ добавления строки к строковой переменной-использовать + или +=. Это потому, что он читаемый и быстрый. Они также так же быстры, какой из них вы выбираете, это вопрос вкуса, последний является наиболее распространенным. Вот тайминги с timeit модуль:

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

однако те, кто рекомендует иметь списки и добавлять к ним, а затем присоединяться к этим спискам, делают это, потому что добавление строки в список, по-видимому, очень быстро по сравнению с расширением строки. И в некоторых случаях это может быть правдой. Вот, например, один миллион добавлений односимвольной строки, сначала к строке, затем к списку:

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

OK, оказывается, что даже когда результирующая строка длиной в миллион символов, добавление было еще быстрее.

теперь давайте попробуем добавить тысячу символов длинной строки сто тысяч раз:

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

таким образом, конечная строка заканчивается примерно 100 МБ длинный. Это было довольно медленно, добавление к списку было намного быстрее. Что это время не включает в себя заключительный a.join(). Так сколько времени это займет?

a.join(a):
0.43739795684814453

Oups. Оказывается, даже в этом случае append / join медленнее.

Итак, откуда взялась эта рекомендация? В 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 перестал обновляться, и вы должны остановить использовать его. :-)

(Update: оказывается, когда я делал тестирование более тщательно, используя + и += быстрее для двух строк на Python 2.3, а также. Рекомендация использовать ''.join() должно быть недоразумение)

однако, это CPython. Другие реализации могут иметь другие проблемы. И это еще одна причина, по которой преждевременная оптимизация корень всех зол. Не используйте технику, которая должна быть "быстрее", если вы сначала не измерите ее.

поэтому "лучшая" версия для конкатенации строк-использовать + или +=. И если это окажется медленным для вас, что довольно маловероятно, тогда сделайте что-нибудь еще.

Итак, почему я использую много добавления / присоединения в своем коде? Потому что иногда все становится яснее. Особенно, когда все, что вы должны объединить вместе, должно быть разделено пробелами или запятые, или новые строки.


если вы обьединении много значений, то ни. Добавление списка стоит дорого. Для этого вы можете использовать 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)

из FAQ python:каков наиболее эффективный способ объединения многих строк вместе?

str и байты объекты неизменны, поэтому объединяют многие строки вместе неэффективны, поскольку каждая конкатенация создает новую объект. В общем случае общая стоимость выполнения является квадратичной в общая длина строки.

чтобы накопить много объектов str, рекомендуемая идиома-разместить их в список и позвоните str.join() в конце:

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(еще одна разумно эффективная идиома-использовать io.StringIO)

накопить много байтов объекты, рекомендуемой идиомой является расширение объект bytearray, использующий конкатенацию на месте (оператор+=):

result = bytearray()
for b in my_bytes_objects:
    result += b

Edit: я был глуп и результаты были вставлены назад, что делает его похожим на добавление в список быстрее, чем cStringIO. Я также добавил тесты для ByteArray / str concat, а также второй раунд тестов с использованием большего списка с большими строками. (python 2.7.3)

пример теста ipython для большого списки строк

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

рекомендуемый метод по-прежнему использовать append и join.


использование вместо конкатенации строк ПО " + " является худшим методом конкатенации с точки зрения стабильности и перекрестной реализации, поскольку он не поддерживает все значения. Стандарт PEP8 препятствует этому и поощряет использование format(), join() и append() для долгосрочного использования.


Если объединяемые строки являются литералами, используйте конкатенация строковых литералов

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

Это полезно, если вы хотите прокомментировать часть строки (как выше) или если вы хотите использовать сырые строки или тройные кавычки для части литерала, но не для всех.

происходит на синтаксис слоя используется нулевой операторы конкатенации.

в то время как несколько устарел,код, как Pythonista: идиоматический Python рекомендует join() над + в этом разделе. Как и PythonSpeedPerformanceTips в разделе конкатенация строк, со следующей оговоркой:

точность этого раздела оспаривается в отношении более позднего версия Python. В CPython 2.5 конкатенация строк довольно быстро, хотя это может не относиться аналогично другим Python реализации. См. раздел ConcatenationTestCode для обсуждения.


в Python >= 3.6 новая f-строка является эффективным способом объединения строки.

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

вы можете использовать это(более эффективное) тоже. (https://softwareengineering.stackexchange.com/questions/304445/why-is-s-better-than-for-concatenation)

s += "%s" %(stringfromelsewhere)

вы пишете эту функцию

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

как @jdi упоминает Python документация предлагает использовать str.join или io.StringIO для конкатенации строк. И говорит, что разработчик должен ожидать квадратичного времени от += в цикле, хотя есть оптимизация С Python 2.4. As этой ответ:

если 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


мой вариант использования был немного другим. Мне пришлось построить запрос, в котором более 20 полей были динамическими. Я следовал этому подходу использования format method

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

Это было сравнительно проще для меня вместо использования + или других способов