Como anexar uma string a outra no Python?

594

Eu quero uma maneira eficiente de acrescentar uma string a outra no Python, além do seguinte.

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

Existe algum bom método interno para usar?

user469652
fonte
8
TL; DR: Se você está apenas procurando a maneira mais simples de anexar strings, e não se importa com eficiência: #"foo" + "bar" + str(3)
187 Andrew #

Respostas:

609

Se você tiver apenas uma referência a uma string e concatenar outra string até o final, o CPython agora fará casos especiais e tenta estender a string no lugar.

O resultado final é que a operação é amortizada O (n).

por exemplo

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

costumava ser O (n ^ 2), mas agora é O (n).

Na fonte (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;
}

É fácil o suficiente para verificar empiricamente.

$ python -m timeit -s "s = ''" "para i no xrange (10): s + = 'a'"
1000000 loops, o melhor de 3: 1,85 usec por loop
$ python -m timeit -s "s = ''" "para i no xrange (100): s + = 'a'"
10000 loops, o melhor de 3: 16,8 usec por loop
$ python -m timeit -s "s = ''" "para i no xrange (1000): s + = 'a'"
10000 loops, o melhor de 3: 158 usec por loop
$ python -m timeit -s "s = ''" "para i no xrange (10000): s + = 'a'"
1000 loops, o melhor de 3: 1,71 ms por loop
$ python -m timeit -s "s = ''" "para i no xrange (100000): s + = 'a'"
10 loops, o melhor de 3: 14,6 ms por loop
$ python -m timeit -s "s = ''" "para i no xrange (1000000): s + = 'a'"
10 loops, o melhor de 3: 173 ms por loop

É importante, no entanto, observar que essa otimização não faz parte das especificações do Python. É apenas na implementação cPython, tanto quanto eu sei. O mesmo teste empírico em pypy ou jython, por exemplo, pode mostrar o desempenho O (n ** 2) mais antigo.

$ pypy -m timeit -s "s = ''" "para i no xrange (10): s + = 'a'"
10000 loops, o melhor de 3: 90,8 usec por loop
$ pypy -m timeit -s "s = ''" "para i no xrange (100): s + = 'a'"
1000 loops, o melhor de 3: 896 usec por loop
$ pypy -m timeit -s "s = ''" "para i no xrange (1000): s + = 'a'"
100 loops, o melhor de 3: 9,03 ms por loop
$ pypy -m timeit -s "s = ''" "para i no xrange (10000): s + = 'a'"
10 loops, o melhor de 3: 89,5 ms por loop

Até aí tudo bem, mas então,

$ pypy -m timeit -s "s = ''" "para i no xrange (100000): s + = 'a'"
10 loops, o melhor de 3: 12,8 seg por loop

ai ainda pior do que quadrático. Portanto, o pypy está fazendo algo que funciona bem com cadeias curtas, mas apresenta um desempenho ruim para cadeias maiores.

John La Rooy
fonte
14
Interessante. Por "agora", você quer dizer Python 3.x?
Steve Tjoa
10
@ Steve, Não. É pelo menos em 2,6, talvez até 2,5 #
John La Rooy
8
Você citou a PyString_ConcatAndDelfunção, mas incluiu o comentário para _PyString_Resize. Além disso, o comentário realmente não estabelece sua reivindicação sobre o Big-O
Winston Ewert
3
parabéns por explorar um recurso do CPython que fará o rastreamento do código em outras implementações. Mau conselho.
Jean-François Fabre
4
NÃO use isso. O Pep8 declara explicitamente: o código deve ser escrito de uma maneira que não prejudique outras implementações do Python (PyPy, Jython, IronPython, Cython, Psyco e outras) , e dê esse exemplo específico como algo a ser evitado, pois é tão frágil. "".join(str_a, str_b)
Eraw
287

Não otimize prematuramente. Se você não tem motivos para acreditar que há um gargalo de velocidade causado por concatenações de strings, fique com +e +=:

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

Dito isto, se você está buscando algo como o StringBuilder do Java, o idioma canônico do Python é adicionar itens a uma lista e depois usá str.join-los para concatená-los todos no final:

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

s = ''.join(l)
John Kugelman
fonte
Não sei quais são as implicações de velocidade de criar suas strings como listas e depois juntá-las (), mas acho que geralmente é a maneira mais limpa. Eu também tive grandes sucessos com o uso da notação% s em uma string para um mecanismo de modelagem de SQL que escrevi.
Richo
25
@ Richo Usar .join é mais eficiente. O motivo é que as seqüências de caracteres Python são imutáveis; portanto, usar repetidamente s + = more alocará muitas seqüências sucessivamente maiores. .join gerará a sequência final de uma só vez a partir de suas partes constituintes.
Ben
5
@ Ben, houve uma melhora significativa nessa área - veja minha resposta #
John La Rooy
41
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Isso une str1 e str2 com um espaço como separadores. Você também pode fazer "".join(str1, str2, ...). str.join()leva uma iterável, então você teria que colocar as strings em uma lista ou em uma tupla.

Isso é o mais eficiente possível para um método embutido.

Rafe Kettler
fonte
O que acontece, se str1 é empírico? O espaço em branco será definido?
Jürgen K.
38

Não.

Ou seja, na maioria dos casos, é melhor gerar a cadeia inteira de uma só vez, em vez de anexar a uma cadeia existente.

Por exemplo, não faça: obj1.name + ":" + str(obj1.count)

Em vez disso: use "%s:%d" % (obj1.name, obj1.count)

Isso será mais fácil de ler e mais eficiente.

Winston Ewert
fonte
54
Sinto muito não há nada mais fácil de ler do que (string + string) como o primeiro exemplo, o segundo exemplo pode ser mais eficiente, mas não mais legível
JqueryToAddNumbers
23
@ExceptionSlayer, string + string é bastante fácil de seguir. Mas "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>", acho menos legível e propenso a erros, em seguida,"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert
Isso não ajuda em nada quando o que estou tentando fazer é o equivalente aproximado de, digamos, "string. = Confirmdata ()" do PHP / perl ou similar.
Shadur
@ Shahadur, meu argumento é que você deve pensar novamente, realmente deseja fazer algo equivalente ou é melhor uma abordagem totalmente diferente?
Winston Ewert 23/02
1
E, neste caso, a resposta a essa pergunta é "Não, porque essa abordagem não cobrir meu caso de uso"
Shadur
11

O Python 3.6 nos fornece strings de f , que são uma delícia:

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

Você pode fazer quase tudo dentro do aparelho

print(f"1 + 1 == {1 + 1}")        # prints 1 + 1 == 2
Trenton
fonte
10

Se você precisar fazer muitas operações de acréscimo para criar uma cadeia grande, poderá usar o StringIO ou o cStringIO. A interface é como um arquivo. ou seja: você writeacrescenta texto a ele.

Se você está apenas acrescentando duas strings, use +.

Laurence Gonsalves
fonte
9

isso realmente depende da sua aplicação. Se você está repetindo centenas de palavras e deseja anexá-las a uma lista, .join()é melhor. Mas se você estiver montando uma frase longa, é melhor usá-lo +=.

Ramy
fonte
5

Basicamente, não há diferença. A única tendência consistente é que o Python parece estar ficando mais lento a cada versão ... :(


Lista

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

Python 2.7

1 loop, o melhor de 3: 7,34 s por loop

Python 3.4

1 loop, melhor de 3: 7.99 s por loop

Python 3.5

1 loop, o melhor de 3: 8,48 s por loop

Python 3.6

1 loop, melhor de 3: 9,93 s por loop


Corda

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

Python 2.7 :

1 loop, o melhor de 3: 7,41 s por loop

Python 3.4

1 loop, o melhor de 3: 9,08 s por loop

Python 3.5

1 loop, o melhor de 3: 8,82 s por loop

Python 3.6

1 loop, o melhor de 3: 9,24 s por loop

ostrokach
fonte
2
Eu acho que depende. Eu recebo 1.19 se, 992 msrespectivamente, em Python2.7
John La Rooy 6/15
5

anexar strings com a função __add__

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

Resultado

Hello World
Sai Gopi N
fonte
4
str + str2ainda é mais curto.
Nik O'Lai
2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'
Rahul Shrivastava
fonte
1
O código é bom, mas ajudaria a ter uma explicação que o acompanha. Por que usar esse método e não as outras respostas nesta página?
Cgmb
11
O uso a.__add__(b)é idêntico à escrita a+b. Quando você concatena as strings usando o +operador, o Python chama o __add__método na string do lado esquerdo passando a string do lado direito como parâmetro.
Addie