¿Cuál es el método de concatenación de cadenas más eficiente en python?

¿Existe algún método eficiente de concatenación de cadenas en masa en Python (como StringBuilder en C # o StringBuffer en Java)? He encontrado los siguientes métodos aquí :

  • Concatenación simple utilizando +
  • Usando lista de cadenas y método de join
  • Usando UserString desde el módulo MutableString
  • Usando la matriz de caracteres y el módulo de array
  • Usando cStringIO desde el módulo StringIO

Pero, ¿qué usan o sugieren los expertos, y por qué?

[ Una pregunta relacionada aquí ]

Quizás te interese esto: una anécdota de optimización de Guido. Aunque vale la pena recordar también que este es un artículo antiguo y es anterior a la existencia de cosas como ''.join (aunque creo que string.joinfields es más o menos lo mismo)

En base a eso, el módulo de array puede ser más rápido si puede encajar su problema. Pero ''.join es probablemente lo suficientemente rápido y tiene la ventaja de ser idiomático y, por lo tanto, más fácil de entender para otros progtwigdores de Python.

Finalmente, la regla de oro de la optimización: no optimice a menos que sepa que necesita, y mida en lugar de adivinar.

Puedes medir diferentes métodos usando el módulo timeit . Eso puede decirle cuál es más rápido, en lugar de extraños al azar en Internet, haciendo conjeturas.

''.join(sequenceofstrings) es lo que generalmente funciona mejor: más simple y más rápido.

Depende de lo que estés haciendo.

Después de Python 2.5, la concatenación de cadenas con el operador + es bastante rápida. Si solo está concatenando un par de valores, usar el operador + funciona mejor:

 >>> x = timeit.Timer(stmt="'a' + 'b'") >>> x.timeit() 0.039999961853027344 >>> x = timeit.Timer(stmt="''.join(['a', 'b'])") >>> x.timeit() 0.76200008392333984 

Sin embargo, si está juntando una cadena en un bucle, es mejor que utilice el método de unión a lista:

 >>> join_stmt = """ ... joined_str = '' ... for i in xrange(100000): ... joined_str += str(i) ... """ >>> x = timeit.Timer(join_stmt) >>> x.timeit(100) 13.278000116348267 >>> list_stmt = """ ... str_list = [] ... for i in xrange(100000): ... str_list.append(str(i)) ... ''.join(str_list) ... """ >>> x = timeit.Timer(list_stmt) >>> x.timeit(100) 12.401000022888184 

… pero tenga en cuenta que debe estar juntando un número relativamente alto de cadenas antes de que la diferencia sea notable.

Python 3.6 cambió el juego para la concatenación de cadenas de componentes conocidos con la interpolación de cadenas literal .

Dado el caso de prueba de la respuesta de mkoistinen , tener cuerdas

 domain = 'some_really_long_example.com' lang = 'en' path = 'some/really/long/path/' 

Los contendientes son

  • f'http://{domain}/{lang}/{path}'0.151 µs

  • 'http://%s/%s/%s' % (domain, lang, path) – 0.321 µs

  • 'http://' + domain + '/' + lang + '/' + path – 0.356 µs

  • ''.join(('http://', domain, '/', lang, '/', path))0.249 µs (observe que la construcción de una tupla de longitud constante es ligeramente más rápida que la creación de una lista constante).

Así, en la actualidad, el código más corto y más bello posible también es el más rápido.

En las versiones alfa de Python 3.6, la implementación de f'' cadenas f'' fue la más lenta posible: en realidad, el código de byte generado es bastante equivalente al caso ''.join() con llamadas innecesarias a str.__format__ que sin argumentos solo se devolvería sin cambios. . Estas ineficiencias fueron abordadas antes de 3.6 final.

La velocidad se puede contrastar con el método más rápido para Python 2, que es + concatenación en mi computadora; y eso toma 0.203 µs con cadenas de 8 bits, y 0.259 µs si todas las cadenas son Unicode.

Según la respuesta de John Fouhy, no optimice a menos que tenga que hacerlo, pero si está aquí y hace esta pregunta, puede ser precisamente porque tiene que hacerlo . En mi caso, necesitaba ensamblar algunas URL a partir de variables de cadena … rápido. Me di cuenta de que nadie (hasta ahora) parece estar considerando el método de formato de cadena, así que pensé que lo intentaría y, sobre todo por un poco de interés, pensé que iba a lanzar al operador de interpolación de cadenas allí para una buena medición. Para ser honesto, no pensé que ninguno de estos se acumularía para una operación ‘+’ directa o un ” .join (). ¿Pero adivina que? En mi sistema Python 2.7.5, el operador de interpolación de cadenas los controla a todos y string.format () es el que tiene el peor desempeño:

 # concatenate_test.py from __future__ import print_function import timeit domain = 'some_really_long_example.com' lang = 'en' path = 'some/really/long/path/' iterations = 1000000 def meth_plus(): '''Using + operator''' return 'http://' + domain + '/' + lang + '/' + path def meth_join(): '''Using ''.join()''' return ''.join(['http://', domain, '/', lang, '/', path]) def meth_form(): '''Using string.format''' return 'http://{0}/{1}/{2}'.format(domain, lang, path) def meth_intp(): '''Using string interpolation''' return 'http://%s/%s/%s' % (domain, lang, path) plus = timeit.Timer(stmt="meth_plus()", setup="from __main__ import meth_plus") join = timeit.Timer(stmt="meth_join()", setup="from __main__ import meth_join") form = timeit.Timer(stmt="meth_form()", setup="from __main__ import meth_form") intp = timeit.Timer(stmt="meth_intp()", setup="from __main__ import meth_intp") plus.val = plus.timeit(iterations) join.val = join.timeit(iterations) form.val = form.timeit(iterations) intp.val = intp.timeit(iterations) min_val = min([plus.val, join.val, form.val, intp.val]) print('plus %0.12f (%0.2f%% as fast)' % (plus.val, (100 * min_val / plus.val), )) print('join %0.12f (%0.2f%% as fast)' % (join.val, (100 * min_val / join.val), )) print('form %0.12f (%0.2f%% as fast)' % (form.val, (100 * min_val / form.val), )) print('intp %0.12f (%0.2f%% as fast)' % (intp.val, (100 * min_val / intp.val), )) 

Los resultados:

 # python2.7 concatenate_test.py plus 0.360787868500 (90.81% as fast) join 0.452811956406 (72.36% as fast) form 0.502608060837 (65.19% as fast) intp 0.327636957169 (100.00% as fast) 

Si utilizo un dominio más corto y una ruta más corta, la interpolación aún gana. La diferencia es más pronunciada, sin embargo, con cuerdas más largas.

Ahora que tenía un buen script de prueba, también lo probé en Python 2.6, 3.3 y 3.4, aquí están los resultados. En Python 2.6, el operador más es el más rápido! En Python 3, unirse gana. Nota: estas pruebas son muy repetibles en mi sistema. Entonces, ‘más’ siempre es más rápido en 2.6, ‘intp’ siempre es más rápido en 2.7 y ‘unirse’ siempre es más rápido en Python 3.x.

 # python2.6 concatenate_test.py plus 0.338213920593 (100.00% as fast) join 0.427221059799 (79.17% as fast) form 0.515371084213 (65.63% as fast) intp 0.378169059753 (89.43% as fast) # python3.3 concatenate_test.py plus 0.409130576998 (89.20% as fast) join 0.364938726001 (100.00% as fast) form 0.621366866995 (58.73% as fast) intp 0.419064424001 (87.08% as fast) # python3.4 concatenate_test.py plus 0.481188605998 (85.14% as fast) join 0.409673971997 (100.00% as fast) form 0.652010936996 (62.83% as fast) intp 0.460400978001 (88.98% as fast) # python3.5 concatenate_test.py plus 0.417167026084 (93.47% as fast) join 0.389929617057 (100.00% as fast) form 0.595661019906 (65.46% as fast) intp 0.404455224983 (96.41% as fast) 

Lección aprendida:

  • A veces, mis suposiciones están totalmente equivocadas.
  • Prueba contra el sistema env. Estarás corriendo en producción.
  • ¡La interpolación de cuerdas no está muerta todavía!

tl; dr:

  • Si usas 2.6, usa el operador +.
  • si está utilizando 2.7 use el operador ‘%’.
  • si está utilizando 3.x use ” .join ().

depende en gran medida de los tamaños relativos de la nueva cadena después de cada nueva concatenación. Con el operador + , para cada concatenación se crea una nueva cadena. Si las cadenas intermedias son relativamente largas, el + vuelve cada vez más lento porque la nueva cadena intermedia está siendo almacenada.

Considere este caso:

 from time import time stri='' a='aagsdfghfhdyjddtyjdhmfghmfgsdgsdfgsdfsdfsdfsdfsdfsdfddsksarigqeirnvgsdfsdgfsdfgfg' l=[] #case 1 t=time() for i in range(1000): stri=stri+a+repr(i) print time()-t #case 2 t=time() for i in xrange(1000): l.append(a+repr(i)) z=''.join(l) print time()-t #case 3 t=time() for i in range(1000): stri=stri+repr(i) print time()-t #case 4 t=time() for i in xrange(1000): l.append(repr(i)) z=''.join(l) print time()-t 

Resultados

1 0.00493192672729

2 0.000509023666382

3 0.00042200088501

4 0.000482797622681

En el caso de 1 y 2, agregamos una cadena grande y join () se realiza 10 veces más rápido. En el caso 3 y 4, agregamos una cadena pequeña, y ‘+’ se ejecuta un poco más rápido

Me encontré con una situación en la que necesitaba tener una cadena anexable de tamaño desconocido. Estos son los resultados de referencia (python 2.7.3):

 $ python -m timeit -s 's=""' 's+="a"' 10000000 loops, best of 3: 0.176 usec per loop $ python -m timeit -s 's=[]' 's.append("a")' 10000000 loops, best of 3: 0.196 usec per loop $ python -m timeit -s 's=""' 's="".join((s,"a"))' 100000 loops, best of 3: 16.9 usec per loop $ python -m timeit -s 's=""' 's="%s%s"%(s,"a")' 100000 loops, best of 3: 19.4 usec per loop 

Esto parece mostrar que ‘+ =’ es el más rápido. Los resultados del enlace skymind están un poco desactualizados.

(Me doy cuenta de que el segundo ejemplo no está completo, la lista final debería unirse. Sin embargo, esto demuestra que simplemente preparar la lista lleva más tiempo que la cadena concat).

Un año más tarde, probemos la respuesta de mkoistinen con python 3.4.3:

  • más 0.963564149000 (95.83% tan rápido)
  • unirse a 0.923408469000 (100.00% tan rápido)
  • formulario 1.501130934000 (61.51% tan rápido)
  • intp 1.019677452000 (90.56% tan rápido)

Nada ha cambiado. Unirse sigue siendo el método más rápido. Sin embargo, dado que intp es la mejor opción en términos de legibilidad, es posible que desee utilizar intp.

Inspirado por los puntos de referencia de @ JasonBaker, aquí hay uno simple que compara 10 cadenas "abcdefghijklmnopqrstuvxyz" , que muestran que .join() es más rápido; incluso con este pequeño aumento en las variables:

Cadena

 >>> x = timeit.Timer(stmt='"abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz" + "abcdefghijklmnopqrstuvxyz"') >>> x.timeit() 0.9828147209324385 

Unirse

 >>> x = timeit.Timer(stmt='"".join(["abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz", "abcdefghijklmnopqrstuvxyz"])') >>> x.timeit() 0.6114138159765048 

Para un pequeño conjunto de cadenas cortas (es decir, 2 o 3 cadenas de no más de unos pocos caracteres), más aún es mucho más rápido. Usando el maravilloso guión de mkoistinen en Python 2 y 3:

 plus 2.679107467004 (100.00% as fast) join 3.653773699996 (73.32% as fast) form 6.594011374000 (40.63% as fast) intp 4.568015249999 (58.65% as fast) 

Entonces, cuando su código está haciendo una gran cantidad de pequeñas concatenaciones separadas, además, es la forma preferida si la velocidad es crucial.

Probablemente “las nuevas cadenas de caracteres en Python 3.6” es la forma más eficiente de concatenar cadenas.

Utilizando% s

 >>> timeit.timeit("""name = "Some" ... age = 100 ... '%s is %s.' % (name, age)""", number = 10000) 0.0029734770068898797 

Utilizando .format

 >>> timeit.timeit("""name = "Some" ... age = 100 ... '{} is {}.'.format(name, age)""", number = 10000) 0.004015227983472869 

Usando f

 >>> timeit.timeit("""name = "Some" ... age = 100 ... f'{name} is {age}.'""", number = 10000) 0.0019175919878762215 

Fuente: https://realpython.com/python-f-strings/