Як додати один рядок до іншого в Python?


593

Я хочу ефективний спосіб додавання однієї рядка до іншої в Python, крім наступного.

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

Чи є якийсь хороший вбудований метод для використання?


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 Usec на цикл
$ python -m timeit -s "s = ''" "для i в xrange (100): s + = 'a'"
10000 петель, найкраще 3: 16,8 Usec за петлю
$ python -m timeit -s "s = ''" "для i в xrange (1000): s + = 'a'"
10000 петель, найкраще 3: 158 Usec за петлю
$ 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 Usec за петлю
$ pypy -m timeit -s "s = ''" "для i в xrange (100): s + = 'a'"
1000 петель, найкраще 3: 896 Usec на петлю
$ 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
@Steve, Ні. Принаймні в 2,6, можливо, навіть у 2,5
Джон Ла Руй

8
Ви цитували PyString_ConcatAndDelфункцію, але включили коментар _PyString_Resize. Також коментар насправді не підтверджує вашу претензію щодо Big-O
Вінстон Еверт

3
поздоровлення з використанням функції CPython, яка змусить сканувати код в інших реалізаціях. Погана порада.
Жан-Франсуа Фабре


287

Не передчасно оптимізуйте. Якщо у вас немає підстав вважати, що існує вузьке вузьке місце, викликане струнними об'єднаннями, тоді просто дотримуйтесь +та +=:

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

Це означає, що якщо ви прагнете щось на зразок StringBuilder Java, канонічна ідіома Python полягає в тому, щоб додати елементи до списку, а потім використати str.joinдля об'єднання їх у кінці:

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

s = ''.join(l)

Я не знаю, які наслідки для швидкості створення ваших рядків як списків, а потім .join () ing є, але я вважаю, що це, як правило, найчистіший спосіб. Я також мав великі успіхи з використанням позначення% s у рядку для двигуна шаблонів SQL, про який я писав.
Річо

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 сповільнений? Чи буде встановлено пробіл?
Юрген К.

38

Не варто.

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

Наприклад, не робіть: obj1.name + ":" + str(obj1.count)

Натомість: використовуйте "%s:%d" % (obj1.name, obj1.count)

Це буде легше читати та ефективніше.


54
Вибачте, що немає нічого легшого для читання, ніж (рядок + рядок), як перший приклад, другий приклад може бути більш ефективним, але не більш читабельним
JqueryToAddNumbers

23
@ExceptionSlayer, string + string досить легко дотримуватися. Але "<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. = Verifydata ()" або подібного.
Шадур

@Shadur, моя думка полягає в тому, що ви повинні подумати ще раз, чи дійсно ви хочете зробити щось еквівалентне, або зовсім інший підхід кращий?
Вінстон Еверт

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 с на петлю

Пітон 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 с на петлю

Пітон 3.6

1 петля, найкраще з 3: 9,24 с на петлю


2
Я думаю, це залежить. Я отримую 1.19 sі 992 msвідповідно на Python2.7
John La


2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'

1
Код приємний, але це допомогло б отримати супровідне пояснення. Навіщо використовувати цей метод, а не інші відповіді на цій сторінці?
cgmb

11
Використовувати a.__add__(b)ідентично письмові a+b. Коли ви об'єднуєте рядки за допомогою +оператора, Python викличе __add__метод у рядку з лівого боку, передаючи рядок правої сторони як параметр.
Адді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.