Je veux un moyen efficace pour ajouter une chaîne à une autre en Python.
var1 = "foo"
var2 = "bar"
var3 = var1 + var2
Existe-t-il une bonne méthode intégrée à utiliser?
Si vous n'avez qu'une seule référence à une chaîne et que vous concaténez une autre chaîne jusqu'à la fin, CPython met maintenant ce cas en attente et tente d'étendre la chaîne à la 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)
l'habitude d'être 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;
}
Il est assez facile de vérifier empiriquement.
$ python -m timeit -s "s = ''" "pour i dans xrange (10): s + = 'a'" 1000000 boucles, meilleur des 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, au mieux de 3: 158 usec par boucle $ python -m timeit -s "s = ''" "pour i dans xrange (10000): s + = 'a' " 1000 boucles, meilleure de 3: 1,71 ms 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 in xrange (1000000): s + = 'a' " 10 boucles, le meilleur de 3: 173 ms par boucle
C'est important Cependant, notez que cette optimisation ne fait pas partie de la spécification Python. C'est seulement dans l'implémentation de cPython, autant que je sache. Les mêmes tests empiriques sur pypy ou jython, par exemple, pourraient montrer les performances plus anciennes de 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 msec par boucle $ pypy -m timeit -s "s = ''" "pour i dans la plage (10000): s + = 'a'" 10 boucles, meilleur de 3: 89,5 msec par boucle
Jusqu'ici tout va bien, mais alors,
$ pypy -m timeit -s "s = ''" "pour i dans xrange (100000): s + = 'a'" 10 boucles, au mieux en 3: 12,8 secondes par boucle
aïe pire que le quadratique. Donc, pypy fait quelque chose qui fonctionne bien avec des chaînes courtes, mais qui fonctionne mal pour les grandes chaînes.
Ne pas optimiser prématurément. Si vous n'avez aucune raison de penser que les concaténations de chaînes ont un goulot d'étranglement au niveau de la vitesse, il vous suffit de vous en tenir à +
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 à utiliser str.join
pour les concaténer à la fin:
l = []
l.append('foo')
l.append('bar')
l.append('baz')
s = ''.join(l)
Ne pas.
C'est-à-dire que dans la plupart des cas, il est préférable de générer la chaîne entière en une fois plutôt que de l'ajouter à une chaîne existante.
Par exemple, ne faites pas: obj1.name + ":" + str(obj1.count)
Au lieu de cela: utilisez "%s:%d" % (obj1.name, obj1.count)
Ce sera plus facile à lire et plus efficace.
str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))
Cela joint str1 et str2 avec un espace comme séparateur. Vous pouvez également faire "".join(str1, str2, ...)
. str.join()
prend une valeur itérable, vous devez donc placer 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.
Si vous devez effectuer de nombreuses opérations d’ajout pour construire une chaîne longue, vous pouvez utiliser StringIO ou cStringIO. L'interface est comme un fichier. c'est-à-dire que vous write
pour y ajouter du texte.
Si vous ajoutez seulement 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 à une liste, .join()
est préférable. Mais si vous composez une longue phrase, il vaut mieux utiliser +=
.
Python 3.6 nous donne f-strings , 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
En gros, pas de différence. La seule tendance constante est que Python semble ralentir à 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, le meilleur de 3: 9.9 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, le meilleur de 3: 8.82 s par boucle
Python 3.6
1 boucle, meilleur de 3: 9.24 s par boucle
a='foo'
b='baaz'
a.__add__(b)
out: 'foobaaz'
ajoute des chaînes avec _ ADD _ function
str = "Hello"
str2 = " World"
st = str.__add__(str2)
print(st)
Sortie
Hello World