Comment ajouter une chaîne à une autre en Python?

594

Je veux un moyen efficace d'ajouter une chaîne à une autre en Python, autre que le suivant.

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

Y a-t-il une bonne méthode intégrée à utiliser?

user469652
la source
8
TL; DR: Si vous cherchez simplement un moyen simple d'ajouter des chaînes et que vous ne vous souciez pas de l'efficacité:"foo" + "bar" + str(3)
Andrew

Réponses:

609

Si vous ne disposez que d'une seule référence à une chaîne et que vous concaténez une autre chaîne à la fin, CPython applique maintenant des cas spéciaux à cette situation et tente d'étendre la chaîne en place.

Le résultat final est que l'opération est amortie O (n).

par exemple

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

était O (n ^ 2), mais maintenant c'est O (n).

Depuis la source (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;
}

C'est assez facile à vérifier empiriquement.

$ python -m timeit -s "s = ''" "pour i dans xrange (10): s + = 'a'"
1000000 boucles, meilleur de 3: 1,85 usec par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (100): s + = 'a'"
10000 boucles, meilleur de 3: 16,8 usec par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (1000): s + = 'a'"
10000 boucles, le meilleur de 3: 158 usec par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (10000): s + = 'a'"
1000 boucles, meilleur de 3: 1,71 msec par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (100000): s + = 'a'"
10 boucles, meilleur de 3: 14,6 ms par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (1000000): s + = 'a'"
10 boucles, le meilleur de 3: 173 ms par boucle

Il est cependant important de noter que cette optimisation ne fait pas partie de la spécification Python. Ce n'est que dans l'implémentation de cPython que je sache. Les mêmes tests empiriques sur pypy ou jython par exemple pourraient montrer les anciennes performances O (n ** 2).

$ pypy -m timeit -s "s = ''" "pour i dans xrange (10): s + = 'a'"
10000 boucles, meilleur de 3: 90,8 usec par boucle
$ pypy -m timeit -s "s = ''" "pour i dans xrange (100): s + = 'a'"
1000 boucles, meilleur de 3: 896 usec par boucle
$ pypy -m timeit -s "s = ''" "pour i dans xrange (1000): s + = 'a'"
100 boucles, meilleur de 3: 9,03 ms par boucle
$ pypy -m timeit -s "s = ''" "pour i dans xrange (10000): s + = 'a'"
10 boucles, meilleur de 3: 89,5 ms par boucle

Jusqu'ici tout va bien, mais alors,

$ pypy -m timeit -s "s = ''" "pour i dans xrange (100000): s + = 'a'"
10 boucles, meilleur de 3: 12,8 s par boucle

aïe encore pire que quadratique. Donc, Pypy fait quelque chose qui fonctionne bien avec des chaînes courtes, mais fonctionne mal pour les chaînes plus grandes.

John La Rooy
la source
14
Intéressant. Par "maintenant", voulez-vous dire Python 3.x?
Steve Tjoa
10
@Steve, Non. C'est au moins en 2.6 peut-être même 2.5
John La Rooy
8
Vous avez cité la PyString_ConcatAndDelfonction mais inclus le commentaire pour _PyString_Resize. De plus, le commentaire n'établit pas vraiment votre affirmation concernant le Big-O
Winston Ewert
3
Félicitations pour avoir exploité une fonctionnalité CPython qui fera analyser le code sur d'autres implémentations. Mauvais conseil.
Jean-François Fabre
4
Ne l'utilisez PAS. Pep8 déclare explicitement: le code doit être écrit d'une manière qui ne désavantage pas les autres implémentations de Python (PyPy, Jython, IronPython, Cython, Psyco, etc.), il donne ensuite cet exemple spécifique comme quelque chose à éviter car il est si fragile."".join(str_a, str_b)
Eraw
287

N'optimisez pas prématurément. Si vous n'avez aucune raison de croire qu'il y a un goulot d'étranglement de vitesse causé par les concaténations de chaînes, alors restez avec +et +=:

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

Cela dit, si vous visez quelque chose comme StringBuilder de Java, l'idiome canonique Python consiste à ajouter des éléments à une liste, puis str.joinà les concaténer tous à la fin:

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

s = ''.join(l)
John Kugelman
la source
Je ne sais pas quelles sont les implications de vitesse de la construction de vos chaînes sous forme de listes, puis .join () ing, mais je trouve que c'est généralement la manière la plus propre. J'ai également eu de grands succès avec l'utilisation de la notation% s dans une chaîne pour un moteur de création de modèles SQL que j'ai écrit.
richo
25
@Richo L'utilisation de .join est plus efficace. La raison en est que les chaînes Python sont immuables, donc l'utilisation répétée de s + = more allouera beaucoup de chaînes successivement plus grandes. .join générera la chaîne finale en une seule fois à partir de ses parties constituantes.
Ben
5
@Ben, il y a eu une amélioration significative dans ce domaine - voir ma réponse
John La Rooy
41
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Cela joint str1 et str2 avec un espace comme séparateurs. Vous pouvez aussi le faire "".join(str1, str2, ...).str.join()prend un itérable, vous devez donc mettre les chaînes dans une liste ou un tuple.

C'est à peu près aussi efficace que possible pour une méthode intégrée.

Rafe Kettler
la source
Que se passe-t-il si str1 est empy? L'espace sera-t-il défini?
Jürgen K.
38

Non.

Autrement dit, dans la plupart des cas, il vaut mieux générer la chaîne entière en une seule fois plutôt que de l'ajouter à une chaîne existante.

Par exemple, ne faites pas: obj1.name + ":" + str(obj1.count)

À la place: utilisez "%s:%d" % (obj1.name, obj1.count)

Ce sera plus facile à lire et plus efficace.

Winston Ewert
la source
54
je suis désolé, il n'y a rien de plus facile à lire que (chaîne + chaîne) comme le premier exemple, le deuxième exemple pourrait être plus efficace, mais pas plus lisible
JqueryToAddNumbers
23
@ExceptionSlayer, string + string est assez facile à suivre. Mais "<div class='" + className + "' id='" + generateUniqueId() + "'>" + message_text + "</div>"je trouve alors moins lisible et sujet aux erreurs"<div class='{classname}' id='{id}'>{message_text}</div>".format(classname=class_name, message_text=message_text, id=generateUniqueId())
Winston Ewert
Cela n'aide pas du tout quand ce que j'essaie de faire est l'équivalent approximatif de, disons, la "chaîne. = Verifydata ()" de PHP / perl ou similaire.
Shadur
@Shadur, mon point est que vous devriez réfléchir à nouveau, voulez-vous vraiment faire quelque chose d'équivalent, ou une approche entièrement différente est-elle meilleure?
Winston Ewert
1
Et dans ce cas, la réponse à cette question est "Non, car cette approche ne couvre pas mon cas d'utilisation"
Shadur
11

Python 3.6 nous donne des chaînes f , qui sont un délice:

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

Vous pouvez faire presque tout à l'intérieur des accolades

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

Si vous devez effectuer de nombreuses opérations d'ajout pour créer une grande chaîne, vous pouvez utiliser StringIO ou cStringIO. L'interface est comme un fichier. c'est à dire: vouswrite pour y ajouter du texte.

Si vous ajoutez simplement deux chaînes, utilisez simplement +.

Laurence Gonsalves
la source
9

cela dépend vraiment de votre application. Si vous parcourez des centaines de mots et que vous souhaitez tous les ajouter dans une liste, .join()c'est mieux. Mais si vous préparez une longue phrase, vous feriez mieux de l'utiliser +=.

Ramy
la source
5

Fondamentalement, aucune différence. La seule tendance constante est que Python semble ralentir avec chaque version ... :(


liste

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

Python 2.7

1 boucle, meilleur de 3: 7,34 s par boucle

Python 3.4

1 boucle, meilleur de 3: 7,99 s par boucle

Python 3.5

1 boucle, meilleur de 3: 8,48 s par boucle

Python 3.6

1 boucle, meilleur de 3: 9,93 s par boucle


Chaîne

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

Python 2.7 :

1 boucle, meilleur de 3: 7,41 s par boucle

Python 3.4

1 boucle, meilleur de 3: 9,08 s par boucle

Python 3.5

1 boucle, meilleur de 3: 8,82 s par boucle

Python 3.6

1 boucle, meilleur de 3: 9,24 s par boucle

ostrokach
la source
2
Je suppose que cela dépend. Je reçois 1.19 set 992 msrespectivement sur Python2.7
John La Rooy
5

ajouter des chaînes avec la fonction __add__

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

Production

Hello World
Sai Gopi N
la source
4
str + str2est encore plus court.
Nik O'Lai
2
a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'
Rahul Shrivastava
la source
1
Le code est bien, mais il serait utile d'avoir une explication d'accompagnement. Pourquoi utiliser cette méthode plutôt que les autres réponses sur cette page?
cgmb
11
L'utilisation a.__add__(b)est identique à l'écriture a+b. Lorsque vous concaténez des chaînes à l'aide de l' +opérateur, Python appellera la __add__méthode sur la chaîne du côté gauche en passant la chaîne de droite en tant que paramètre.
Addie