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?
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?
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
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.
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 +=
:
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)
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.
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.
"<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:
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
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 +
.
Fondamentalement, aucune différence. La seule tendance constante est que Python semble ralentir avec chaque version ... :(
%%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
%%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
1.19 s
et 992 ms
respectivement sur Python2.7
ajouter des chaînes avec la fonction __add__
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Production
Hello World
str + str2
est encore plus court.
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
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)