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


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

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

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


467
2017-12-14 01:41


origine


Réponses:


Si vous avez seulement une référence à une chaîne et que vous concaténénez une autre chaîne à la fin, CPython en fait maintenant cas spéciaux et essaie 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)

l'habitude d'être O (n ^ 2), mais maintenant c'est O (n).

De 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, le meilleur de 3: 1,85 usec par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (100): s + = 'a'"
10000 boucles, le meilleur de 3: 16,8 usec par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (1000): s + = 'a'"
10000 boucles, meilleur du 3: 158 usec par boucle
$ python -m timeit -s "s = ''" "pour i dans xrange (10000): s + = 'a'"
1000 boucles, meilleur 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 dans xrange (1000000): s + = 'a'"
10 boucles, meilleur de 3: 173 ms par boucle

C'est important cependant noter que cette optimisation ne fait pas partie de la spécification Python. C'est seulement dans l'implémentation de cPython à ma connaissance. 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, le meilleur de 3: 896 usec par boucle
$ pypy -m timeit -s "s = ''" "pour i dans xrange (1000): s + = 'a'"
100 boucles, le meilleur de 3: 9.03 msec 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 sec par boucle

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


474
2017-12-14 04:01



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

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

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

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

s = ''.join(l)

232
2017-12-14 01:45



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 seule fois plutôt que d'ajouter une chaîne existante.

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

Au lieu de cela: utiliser "%s:%d" % (obj1.name, obj1.count)

Ce sera plus facile à lire et plus efficace.


34
2017-12-14 02:06



str1 = "Hello"
str2 = "World"
newstr = " ".join((str1, str2))

Cela relie str1 et str2 avec un espace comme séparateurs. Vous pouvez aussi faire "".join(str1, str2, ...). str.join() prend un iterable, donc vous devriez 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.


26
2017-12-14 01:42



Cela dépend vraiment de votre application. Si vous parcourez des centaines de mots et souhaitez tous les ajouter à une liste, .join()est mieux. Mais si vous mettez en place une longue phrase, vous feriez mieux d'utiliser +=.


9
2017-12-14 03:59



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: toi write pour y ajouter du texte.

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


8
2017-12-14 01:42



a='foo'
b='baaz'

a.__add__(b)

out: 'foobaaz'

4
2017-11-20 17:42



Fondamentalement, pas de différence. La seule tendance constante est que Python semble devenir plus lent 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, le meilleur de 3: 7.34 s par boucle

Python 3.4

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

Python 3.5

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

Python 3.6

1 boucle, le 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, le meilleur de 3: 7.41 s par boucle

Python 3.4

1 boucle, le 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, le meilleur de 3: 9.24 s par boucle


3
2017-09-25 01:23



ajouter des chaînes avec __ajouter__ fonction

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

Sortie

Hello World

1
2018-06-18 12:21