Что является предпочтительным способом, чтобы объединить строку в 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?

706   11  

11 ответов:

The лучшие способ добавления строки в строковую переменную, чтобы использовать + или +=. Это потому, что он читается и быстро. Они также так же быстро, какой из них вы выбираете, это вопрос вкуса, последний является наиболее распространенным. Вот тайминги с 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

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

a.join(a):
0.43739795684814453

Oups. Оказывается, даже в этом случае добавление/присоединение происходит медленнее.

так откуда же взялась эта рекомендация? В Python 2?

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

Ну, добавить / присоединиться это умеренно быстрее там, если вы используете очень длинные строки (которые вы обычно не являетесь, что бы у вас была строка это 100 МБ в памяти?)

но настоящий довод-это Python 2.3. Где я даже не покажу вам тайминги, потому что это так медленно, что он еще не закончил. Эти тесты вдруг берут минут. За исключением добавления/соединения, которое так же быстро, как и при более поздних питонах.

Юп. Конкатенация строк была очень медленной в Python еще в каменном веке. Но на 2.4 это уже не так (или, по крайней мере, Python 2.4.7), поэтому рекомендация использовать append/join стала устарела в 2008 году, когда Python 2.3 перестал обновляться, и вы должны остановить использовать его. : -)

(обновление: оказывается, когда я делал тестирование более тщательно, что с помощью + и += быстрее для двух строк на 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() over +в этом разделе. Как и 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. Как этой ответ:

если 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 полей были динамическими. Я следовал этому подходу с использованием метода форматирования

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

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

Comments

    Ничего не найдено.