La concatenación de cadenas vs sustitución de cadenas en Python


En Python, el dónde y cuándo usar concatenación de cadenas versus sustitución de cadenas me elude. Como la concatenación de cadenas ha visto grandes aumentos en el rendimiento, ¿es esto (cada vez más) una decisión estilística en lugar de una práctica?

Para un ejemplo concreto, cómo se debe manejar la construcción de URI flexibles:

DOMAIN = 'http://stackoverflow.com'
QUESTIONS = '/questions'

def so_question_uri_sub(q_num):
    return "%s%s/%d" % (DOMAIN, QUESTIONS, q_num)

def so_question_uri_cat(q_num):
    return DOMAIN + QUESTIONS + '/' + str(q_num)

Editar: También ha habido sugerencias sobre unir una lista de cadenas y para usar la sustitución con nombre. Estas son variantes en el tema central, ¿cuál es, cuál es la forma correcta de hacerlo en qué momento? Gracias por las respuestas!

Author: gotgenes, 2008-12-18

9 answers

La concatenación es (significativamente) más rápida según mi máquina. Pero estilísticamente, estoy dispuesto a pagar el precio de la sustitución si el rendimiento no es crítico. Bueno, y si necesito formatear, no hay necesidad de hacer la pregunta... no hay otra opción que usar interpolación/plantillas.

>>> import timeit
>>> def so_q_sub(n):
...  return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
'http://stackoverflow.com/questions/1000'
>>> def so_q_cat(n):
...  return DOMAIN + QUESTIONS + '/' + str(n)
...
>>> so_q_cat(1000)
'http://stackoverflow.com/questions/1000'
>>> t1 = timeit.Timer('so_q_sub(1000)','from __main__ import so_q_sub')
>>> t2 = timeit.Timer('so_q_cat(1000)','from __main__ import so_q_cat')
>>> t1.timeit(number=10000000)
12.166618871951641
>>> t2.timeit(number=10000000)
5.7813972166853773
>>> t1.timeit(number=1)
1.103492206766532e-05
>>> t2.timeit(number=1)
8.5206360154188587e-06

>>> def so_q_tmp(n):
...  return "{d}{q}/{n}".format(d=DOMAIN,q=QUESTIONS,n=n)
...
>>> so_q_tmp(1000)
'http://stackoverflow.com/questions/1000'
>>> t3= timeit.Timer('so_q_tmp(1000)','from __main__ import so_q_tmp')
>>> t3.timeit(number=10000000)
14.564135316080637

>>> def so_q_join(n):
...  return ''.join([DOMAIN,QUESTIONS,'/',str(n)])
...
>>> so_q_join(1000)
'http://stackoverflow.com/questions/1000'
>>> t4= timeit.Timer('so_q_join(1000)','from __main__ import so_q_join')
>>> t4.timeit(number=10000000)
9.4431309007150048
 55
Author: Vinko Vrsalovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-21 06:41:58

No te olvides de la sustitución de nombre:

def so_question_uri_namedsub(q_num):
    return "%(domain)s%(questions)s/%(q_num)d" % locals()
 23
Author: too much php,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-18 00:22:59

Tenga cuidado de concatenar cadenas en un bucle! El costo de la concatenación de cadenas es proporcional a la longitud del resultado. Looping te lleva directamente a la tierra de N-squared. Algunos lenguajes optimizarán la concatenación a la cadena asignada más recientemente, pero es arriesgado contar con el compilador para optimizar su algoritmo cuadrático hasta lineal. Mejor usar el primitivo (join?) que toma una lista completa de cadenas, hace una sola asignación y las concatena todas en una vez.

 12
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-18 04:40:07

"Como la concatenación de cadenas ha visto grandes aumentos en el rendimiento..."

Si el rendimiento importa, es bueno saberlo.

Sin embargo, los problemas de rendimiento que he visto nunca se han reducido a las operaciones de cadena. Generalmente me he metido en problemas con E / S, clasificación y O(n2) las operaciones son los cuellos de botella.

Hasta que las operaciones de cadena sean los limitadores de rendimiento, me quedaré con las cosas que son obvias. En su mayoría, eso es sustitución cuando es una línea o menos, concatenación cuando tiene sentido, y una herramienta de plantilla (como Mako) cuando es grande.

 11
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-18 00:18:13

Lo que desea concatenar/interpolar y cómo desea formatear el resultado debe conducir su decisión.

  • La interpolación de cadenas le permite agregar fácilmente formato. De hecho, su versión de interpolación de cadenas no hace lo mismo que su versión de concatenación; en realidad agrega una barra diagonal adicional antes del parámetro q_num. Para hacer lo mismo, tendrías que escribir return DOMAIN + QUESTIONS + "/" + str(q_num) en ese ejemplo.

  • La interpolación facilita el formateo numerics; "%d of %d (%2.2f%%)" % (current, total, total/current) sería mucho menos legible en forma de concatenación.

  • La concatenación es útil cuando no tiene un número fijo de elementos a string-ize.

Además, sepa que Python 2.6 introduce una nueva versión de interpolación de cadenas, llamada string templating :

def so_question_uri_template(q_num):
    return "{domain}/{questions}/{num}".format(domain=DOMAIN,
                                               questions=QUESTIONS,
                                               num=q_num)

La plantilla de cadenas está programada para reemplazar eventualmente %-interpolación, pero eso no sucederá por un buen tiempo, creo.

 10
Author: Tim Lesher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-18 00:18:50

Estaba probando la velocidad de diferentes métodos de concatenación/sustitución de cadenas por curiosidad. Una búsqueda en Google sobre el tema me trajo aquí. Pensé que publicaría los resultados de mi prueba con la esperanza de que podría ayudar a alguien a decidir.

    import timeit
    def percent_():
            return "test %s, with number %s" % (1,2)

    def format_():
            return "test {}, with number {}".format(1,2)

    def format2_():
            return "test {1}, with number {0}".format(2,1)

    def concat_():
            return "test " + str(1) + ", with number " + str(2)

    def dotimers(func_list):
            # runs a single test for all functions in the list
            for func in func_list:
                    tmr = timeit.Timer(func)
                    res = tmr.timeit()
                    print "test " + func.func_name + ": " + str(res)

    def runtests(func_list, runs=5):
            # runs multiple tests for all functions in the list
            for i in range(runs):
                    print "----------- TEST #" + str(i + 1)
                    dotimers(func_list)

...Después de ejecutar runtests((percent_, format_, format2_, concat_), runs=5), descubrí que el método % era aproximadamente el doble de rápido que los otros en estas pequeñas cadenas. El método concat siempre fue el más lento (apenas). Hubo diferencias muy pequeñas al cambiar las posiciones en el format() método, pero el cambio de posiciones siempre fue por lo menos .01 más lento que el método de formato regular.

Muestra de resultados de la prueba:

    test concat_()  : 0.62  (0.61 to 0.63)
    test format_()  : 0.56  (consistently 0.56)
    test format2_() : 0.58  (0.57 to 0.59)
    test percent_() : 0.34  (0.33 to 0.35)

Ejecuté estos porque uso concatenación de cadenas en mis scripts, y me preguntaba cuál era el costo. Los ejecuté en diferentes órdenes para asegurarme de que nada interfiriera, o que obtuviera un mejor rendimiento siendo el primero o el último. En una nota al margen, lancé algunos generadores de cuerdas más largos en esas funciones como "%s" + ("a" * 1024) y concat regular fue casi 3 veces tan rápido (1.1 vs 2.8) como usando los métodos format y %. Supongo que depende de las cuerdas, y lo que usted está tratando de lograr. Si el rendimiento realmente importa, podría ser mejor probar cosas diferentes y probarlas. Tiendo a elegir la legibilidad sobre la velocidad, a menos que la velocidad se convierta en un problema, pero eso es solo yo. ASÍ que no me gustó mi copiar / pegar, tuve que poner 8 espacios en todo para que se vea bien. Normalmente uso 4.

 7
Author: Cj Welborn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-13 03:27:35

Recuerde, las decisiones estilísticas son decisiones prácticas, si alguna vez planea mantener o depurar su código : -) Hay una cita famosa de Knuth (posiblemente citando a Hoare?): "Deberíamos olvidarnos de las pequeñas eficiencias, digamos alrededor del 97% de las veces: la optimización prematura es la raíz de todo mal."

Siempre y cuando tengas cuidado de no (decir) convertir una tarea O(n) en una O (n2) tarea, yo iría con lo que encuentre más fácil de entender..

 4
Author: John Fouhy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-18 01:04:35

Uso la sustitución siempre que puedo. Solo uso concatenación si estoy construyendo una cadena en digamos un bucle for.

 0
Author: Draemon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-17 23:56:12

En realidad lo correcto, en este caso (construir rutas) es usar os.path.join. No concatenación o interpolación de cadenas

 -1
Author: hoskeri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-20 03:56:37