una - Concatenación de cadenas vs. sustitución de cadenas en Python
reemplazar caracteres en python (9)
En Python, el lugar y el momento de usar la concatenación de cadenas versus la sustitución de cadenas me elude. Como la concatenación de cuerdas ha visto grandes aumentos en el rendimiento, ¿se trata (más bien) de una decisión estilística más que 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 se han sugerido unirse a una lista de cadenas y usar la sustitución con nombre. Estas son variantes sobre el tema central, ¿cuál es la forma correcta de hacerlo en qué momento? Gracias por las respuestas!
"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. En general, me he metido en problemas con las operaciones de E / S, clasificación y O ( n 2 ) que son los cuellos de botella.
Hasta que las operaciones con cuerdas sean los limitadores del rendimiento, me quedaré con las cosas que son obvias. Principalmente, eso es una sustitución cuando es una línea o menos, concatenación cuando tiene sentido y una herramienta de plantilla (como Mako) cuando es grande.
En realidad, lo correcto, en este caso (construir rutas) es usar os.path.join
. No cadena de concatenación o interpolación
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 más que usar interpolación / plantillas.
>>> import timeit
>>> def so_q_sub(n):
... return "%s%s/%d" % (DOMAIN, QUESTIONS, n)
...
>>> so_q_sub(1000)
''http://.com/questions/1000''
>>> def so_q_cat(n):
... return DOMAIN + QUESTIONS + ''/'' + str(n)
...
>>> so_q_cat(1000)
''http://.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://.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://.com/questions/1000''
>>> t4= timeit.Timer(''so_q_join(1000)'',''from __main__ import so_q_join'')
>>> t4.timeit(number=10000000)
9.4431309007150048
Lo que quiere concatenar / interpolar y cómo quiere formatear el resultado debería impulsar su decisión.
La interpolación de cadenas le permite agregar fácilmente el 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 inclinada adicional antes del parámetro
q_num
. Para hacer lo mismo, debería escribirreturn DOMAIN + QUESTIONS + "/" + str(q_num)
en ese ejemplo.La interpolación facilita el formateo de los números;
"%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 para string -ize.
Además, sepa que Python 2.6 presenta una nueva versión de interpolación de cadenas, llamada plantilla de cadenas :
def so_question_uri_template(q_num):
return "{domain}/{questions}/{num}".format(domain=DOMAIN,
questions=QUESTIONS,
num=q_num)
Las plantillas de cadenas están programadas para reemplazar eventualmente la interpolación%, pero eso no sucederá por un buen tiempo, creo.
No te olvides de la sustitución nombrada:
def so_question_uri_namedsub(q_num):
return "%(domain)s%(questions)s/%(q_num)d" % locals()
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 el 97% del tiempo: La optimización temprana es la raíz de todo mal."
Siempre y cuando tenga cuidado de no convertir (por ejemplo) una tarea O (n) en una tarea O (n 2 ), iré con lo que encuentre más fácil de comprender.
Solo 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 pueda 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 las runtests((percent_, format_, format2_, concat_), runs=5)
ejecución runtests((percent_, format_, format2_, concat_), runs=5)
, encontré que el método% era aproximadamente dos veces más rápido que los demás en estas pequeñas cadenas. El método concat fue siempre el más lento (apenas). Hubo diferencias muy pequeñas al cambiar las posiciones en el método de format()
, pero las posiciones de conmutación siempre fueron al menos .01 más lentas que el método de formato normal.
Muestra de resultados de 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 la concatenación de cadenas en mis scripts, y me preguntaba cuál fue el costo. Los ejecuté en diferentes órdenes para asegurarme de que nada interfiera o que el mejor rendimiento sea el primero o el último. En una nota lateral, agregué algunos generadores de cadena más largos en funciones como "%s" + ("a" * 1024)
y el concat regular era casi 3 veces más rápido (1.1 vs 2.8) que con el format
y %
métodos. Supongo que depende de las cadenas y de lo que estás tratando de lograr. Si el rendimiento realmente importa, podría ser mejor probar diferentes cosas y probarlas. Tiendo a elegir la legibilidad sobre la velocidad, a menos que la velocidad se convierta en un problema, pero así soy yo. ASÍ QUE no me gustó copiar / pegar, tuve que poner 8 espacios en todo para que se vea bien. Usualmente uso 4.
Yo uso la sustitución siempre que puedo. Solo uso la concatenación si estoy construyendo una cadena en un for-loop.
¡Tenga cuidado de concatenar cadenas en un bucle! El costo de la concatenación de cadenas es proporcional a la duración del resultado. Looping te lleva directamente a la tierra de N-cuadrado. 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. Es mejor usar la primitiva (¿ join
?) Que toma una lista completa de cadenas, realiza una sola asignación y las concatena todas de una vez.