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):
voidPyBytes_ConcatAndDel(registerPyObject**pv,registerPyObject*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){registerPyObject*v;registerPyBytesObject*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 */return0;}
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.
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.
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)
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
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.
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
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 +.
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 +=.
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.
"foo" + "bar" + str(3)
Réponses:
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
était O (n ^ 2), mais maintenant c'est O (n).
Depuis la source (bytesobject.c):
C'est assez facile à vérifier empiriquement.
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).
Jusqu'ici tout va bien, mais alors,
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.
la source
PyString_ConcatAndDel
fonction mais inclus le commentaire pour_PyString_Resize
. De plus, le commentaire n'établit pas vraiment votre affirmation concernant le Big-O"".join(str_a, str_b)
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+=
: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:la source
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.
la source
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.
la source
"<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())
Python 3.6 nous donne des chaînes f , qui sont un délice:
Vous pouvez faire presque tout à l'intérieur des accolades
la source
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: vous
write
pour y ajouter du texte.Si vous ajoutez simplement deux chaînes, utilisez simplement
+
.la source
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+=
.la source
Fondamentalement, aucune différence. La seule tendance constante est que Python semble ralentir avec chaque version ... :(
liste
Python 2.7
Python 3.4
Python 3.5
Python 3.6
Chaîne
Python 2.7 :
Python 3.4
Python 3.5
Python 3.6
la source
1.19 s
et992 ms
respectivement sur Python2.7ajouter des chaînes avec la fonction __add__
Production
la source
str + str2
est encore plus court.la source
a.__add__(b)
est identique à l'écriturea+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.