Как добавить одну строку в другую в Python?

594

Я хочу эффективный способ добавить одну строку к другой в Python, кроме следующего.

var1 = "foo"
var2 = "bar"
var3 = var1 + var2

Есть ли хороший встроенный метод для использования?

user469652
источник
8
TL; DR: Если вы просто ищете простой способ добавления строк, и вам не важна эффективность:"foo" + "bar" + str(3)
Эндрю

Ответы:

609

Если у вас есть только одна ссылка на строку, и вы конкатенируете другую строку до конца, CPython теперь использует это в особых случаях и пытается расширить строку на месте.

Конечным результатом является то, что операция амортизируется O (n).

например

s = ""
for i in range(n):
    s+=str(i)

Раньше было O (n ^ 2), но теперь это O (n).

Из источника (bytesobject.c):

void
PyBytes_ConcatAndDel(register PyObject **pv, register PyObject *w)
{
    PyBytes_Concat(pv, w);
    Py_XDECREF(w);
}


/* The following function breaks the notion that strings are immutable:
   it changes the size of a string.  We get away with this only if there
   is only one module referencing the object.  You can also think of it
   as creating a new string object and destroying the old one, only
   more efficiently.  In any case, don't use this if the string may
   already be known to some other part of the code...
   Note that if there's not enough memory to resize the string, the original
   string object at *pv is deallocated, *pv is set to NULL, an "out of
   memory" exception is set, and -1 is returned.  Else (on success) 0 is
   returned, and the value in *pv may or may not be the same as on input.
   As always, an extra byte is allocated for a trailing \0 byte (newsize
   does *not* include that), and a trailing \0 byte is stored.
*/

int
_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
{
    register PyObject *v;
    register PyBytesObject *sv;
    v = *pv;
    if (!PyBytes_Check(v) || Py_REFCNT(v) != 1 || newsize < 0) {
        *pv = 0;
        Py_DECREF(v);
        PyErr_BadInternalCall();
        return -1;
    }
    /* XXX UNREF/NEWREF interface should be more symmetrical */
    _Py_DEC_REFTOTAL;
    _Py_ForgetReference(v);
    *pv = (PyObject *)
        PyObject_REALLOC((char *)v, PyBytesObject_SIZE + newsize);
    if (*pv == NULL) {
        PyObject_Del(v);
        PyErr_NoMemory();
        return -1;
    }
    _Py_NewReference(*pv);
    sv = (PyBytesObject *) *pv;
    Py_SIZE(sv) = newsize;
    sv->ob_sval[newsize] = '\0';
    sv->ob_shash = -1;          /* invalidate cached hash value */
    return 0;
}

Это достаточно просто проверить эмпирически.

$ python -m timeit -s "s = ''" "для i в xrange (10): s + = 'a'"
1000000 петель, лучшее из 3: 1,85 мксек на петлю
$ python -m timeit -s "s = ''" "для i в xrange (100): s + = 'a'"
10000 петель, лучшее из 3: 16,8 усек за петлю
$ python -m timeit -s "s = ''" "для i в xrange (1000): s + = 'a'"
10000 циклов, лучшее из 3: 158 циклов на цикл
$ python -m timeit -s "s = ''" "для i в xrange (10000): s + = 'a'"
1000 циклов, лучшее из 3: 1,71 мсек на цикл
$ python -m timeit -s "s = ''" "для i в xrange (100000): s + = 'a'"
10 циклов, лучшее из 3: 14,6 мсек на цикл
$ python -m timeit -s "s = ''" "для i в xrange (1000000): s + = 'a'"
10 циклов, лучшее из 3: 173 мсек на цикл

Однако важно отметить, что эта оптимизация не является частью спецификации Python. Это только в реализации cPython, насколько я знаю. Например, то же эмпирическое тестирование на pypy или jython может показать более высокую производительность O (n ** 2).

$ pypy -m timeit -s "s = ''" "для i в xrange (10): s + = 'a'"
10000 петель, лучшее из 3: 90,8 мксек на петлю
$ pypy -m timeit -s "s = ''" "для i в xrange (100): s + = 'a'"
1000 циклов, лучшее из 3: 896 циклов на цикл
$ pypy -m timeit -s "s = ''" "для i в xrange (1000): s + = 'a'"
100 циклов, лучшее из 3: 9,03 мсек на цикл
$ pypy -m timeit -s "s = ''" "для i в xrange (10000): s + = 'a'"
10 циклов, лучшее из 3: 89,5 мсек на цикл

Пока все хорошо, но потом,

$ pypy -m timeit -s "s = ''" "для i в xrange (100000): s + = 'a'"
10 циклов, лучшее из 3: 12,8 с на цикл

ой даже хуже, чем квадратичный. Так что pypy делает что-то, что хорошо работает с короткими строками, но плохо работает с большими строками.

Джон Ла Рой
источник
14
Интересно. Под «сейчас» вы подразумеваете Python 3.x?
Стив Тджоа
10
@ Стив, Нет. Это по крайней мере в 2.6, может даже 2.5
Джон Ла Руи
8
Вы процитировали PyString_ConcatAndDelфункцию, но включили комментарий для _PyString_Resize. Кроме того, комментарий на самом деле не подтверждает ваши претензии в отношении Big-O
Уинстон Эверт
3
Поздравляем с использованием функции CPython, которая заставит код сканировать другие реализации. Плохой совет
Жан-Франсуа Фабр
4
НЕ используйте это. Pep8 прямо заявляет: код должен быть написан так, чтобы он не мешал другим реализациям Python (PyPy, Jython, IronPython, Cython, Psyco и т. Д.) , А затем дает конкретный пример, который следует избегать, поскольку он настолько хрупок. Лучшее использование"".join(str_a, str_b)
Eraw
287

Не преждевременно оптимизировать. Если у вас нет причин полагать, что существует узкое место в скорости, вызванное конкатенацией строк, тогда просто придерживайтесь +и +=:

s  = 'foo'
s += 'bar'
s += 'baz'

Тем не менее, если вы стремитесь к чему-то похожему на Java StringBuilder, каноническая идиома Python - добавить элементы в список, а затем использовать str.joinдля объединения их всех в конце:

l = []
l.append('foo')
l.append('bar')
l.append('baz')

s = ''.join(l)
Джон Кугельман
источник
Я не знаю, какова скорость создания ваших строк в виде списков, а затем .join () их, но я считаю, что это, как правило, самый чистый способ. Я также добился больших успехов с использованием% s нотации в строке для механизма шаблонирования SQL, который я написал.
richo
25
@Richo Использование .join более эффективно. Причина в том, что строки Python являются неизменяемыми, поэтому многократное использование s + = more выделяет много последовательных строк большего размера. .join сгенерирует финальную строку за один раз из ее составных частей.
Бен
5
@Ben, в этой области произошло значительное улучшение - см. Мой ответ
Джон Ла Руи
41
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Это объединяет str1 и str2 с пробелом в качестве разделителей. Вы также можете сделать "".join(str1, str2, ...). str.join()занимает многократное повторение, поэтому вам нужно будет поместить строки в список или кортеж.

Это примерно так же эффективно, как и для встроенного метода.

Рэйф Кеттлер
источник
Что происходит, если str1 является empy? Будут ли установлены пробелы?
Юрген К.
38

Не.

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

Например, не делайте: obj1.name + ":" + str(obj1.count)

Вместо этого: используйте "%s:%d" % (obj1.name, obj1.count)

Это будет легче читать и эффективнее.

Уинстон Эверт
источник
54
извините, нет ничего проще для чтения, чем (строка + строка), как в первом примере, второй пример может быть более эффективным, но не более читабельным
JqueryToAddNumbers
23
@ExceptionSlayer, строка + строка довольно проста для отслеживания. Но "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"я нахожу менее читабельным и подверженным ошибкам"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Уинстон Эверт
Это совсем не помогает, когда то, что я пытаюсь сделать, является грубым эквивалентом, скажем, PHP / perl "string. = Verifyydata ()" или подобным.
Шадур
@ Шадур, я хочу сказать, что ты должен снова подумать: ты действительно хочешь сделать что-то эквивалентное, или лучше совсем другой подход?
Уинстон Эверт
1
И в этом случае ответ на этот вопрос «Нет, потому что этот подход не охватывает мой вариант использования»
Шадур
11

Python 3.6 дает нам f-строки , которые восхищают:

var1 = "foo"
var2 = "bar"
var3 = f"{var1}{var2}"
print(var3)                       # prints foobar

Вы можете делать что угодно в фигурных скобках

print(f"1 + 1 == {1 + 1}")        # prints 1 + 1 == 2
Трентон
источник
10

Если вам нужно выполнить много операций добавления для создания большой строки, вы можете использовать StringIO или cStringIO. Интерфейс похож на файл. То есть: вам, writeчтобы добавить текст к нему.

Если вы просто добавляете две строки, просто используйте +.

Лоуренс Гонсалвес
источник
9

это действительно зависит от вашего приложения. Если вы просматриваете сотни слов и хотите добавить их в список, .join()лучше. Но если вы составите длинное предложение, вам лучше использовать +=.

Рами
источник
5

В принципе, без разницы. Единственная устойчивая тенденция заключается в том, что Python становится медленнее с каждой версией ... :(


Список

%%timeit
x = []
for i in range(100000000):  # xrange on Python 2.7
    x.append('a')
x = ''.join(x)

Python 2.7

1 цикл, лучшее из 3: 7,34 с на цикл

Python 3.4

1 цикл, лучшее из 3: 7,99 с на цикл

Python 3.5

1 цикл, лучшее из 3: 8,48 с на цикл

Python 3.6

1 цикл, лучшее из 3: 9,93 с на цикл


строка

%%timeit
x = ''
for i in range(100000000):  # xrange on Python 2.7
    x += 'a'

Python 2.7 :

1 цикл, лучшее из 3: 7,41 с на цикл

Python 3.4

1 цикл, лучшее из 3: 9,08 с на цикл

Python 3.5

1 цикл, лучшее из 3: 8,82 с на цикл

Python 3.6

1 цикл, лучшее из 3: 9,24 с на цикл

ostrokach
источник
2
Я думаю, это зависит Я получаю 1.19 sи 992 msсоответственно на Python2.7
John La Rooy
5

добавлять строки с функцией __add__

str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)

Вывод

Hello World
Саи Гопи Н
источник
4
str + str2все еще короче.
Ник О'Лай
2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'
Рахул Шривастава
источник
1
Код это хорошо, но это поможет получить сопроводительное объяснение. Зачем использовать этот метод, а не другие ответы на этой странице?
cgmb
11
Использование a.__add__(b)идентично письму a+b. Когда вы объединяете строки, используя +оператор, Python вызывает __add__метод для строки слева, передавая строку правой части в качестве параметра.
Адди