Header Ads Widget

Ticker

6/recent/ticker-posts

Cómo formatear una cadena en Python: interpolación, concatenación y más

 Ha pasado un tiempo desde que escribí uno de estos artículos de "cómo hacer", pero he vuelto a hacerlo. Esta vez, quiero hablar sobre el formato de cadenas utilizando técnicas como la interpolación y la concatenación. En otras palabras, es hora de finalmente aprender a formatear una cadena en Python

Introducción al problema

Ya sea que intentemos avisar a un usuario o generar un mensaje de error agradable, el formateo de cadenas siempre puede ser un desafío. Después de todo, la sintaxis varía de un idioma a otro, lo que puede parecer como aprender un metalenguaje. Por ejemplo, en lenguajes como Java y C, el formato de cadenas se basa en la comprensión de conceptos como argumentos de variables y especificadores de formato:

1
printf("Hi, %s", Jeremy);  // Prints "Hi, Jeremy"

Por supuesto, el formato de cadena se vuelve más complicado a medida que presentamos diferentes tipos de datos. Por ejemplo, los números tienen su propio conjunto de especificadores:  %d,  %f, etc, y, que incluso pueden especificar cómo los números se ven en términos de relleno y truncamiento.

Dicho esto, no estoy aquí para aprender el formato de cadenas en C, entonces, ¿cómo logramos lo mismo en Python? En este artículo, analizaremos varios métodos, algunos tontos, solo para ilustrar cuántas formas hay de resolver este problema.

Para comenzar, necesitaremos un ejemplo universal que contenga algunos errores, como mezclar números y cadenas. El siguiente fragmento de código nos servirá como base para el resto del artículo:

1
2
name = "Jeremy"
age = 25

Usando estas variables, queremos construir la siguiente oración:

1
print("My name is Jeremy, and I am 25 years old.")

Por supuesto, siéntase libre de cambiar el nombre y la edad por su nombre y edad.

Soluciones

Resulta que hay bastantes formas de formatear una cadena. Comenzaremos con algunos enfoques directos, luego pasaremos a algunos para encontrar soluciones elegantes.

Dar formato a una cadena mediante la concatenación

Si eres como yo, la concatenación es algo que aprendiste cuando empezaste a codificar. Como resultado, la concatenación puede parecer un atajo rápido para formatear cadenas:

1
print("My name is " + name + ", and I am " + age + " years old.")

Desafortunadamente,  una solución como esta no funcionará . Si intentó ejecutar este código, obtendrá un error desagradable que se parece a esto:

Rastreo (última llamada más reciente):
Archivo “”, línea 1, en
“Mi nombre es” + nombre + “, y tengo” + edad + ”años”.
TypeError: solo puede concatenar str (no "int") a str

Con suerte,  TypeError le da la pista de que al intérprete no le gusta cuando intentamos concatenar una cadena con un número entero. En otras palabras, necesitamos convertir la  age variable en una cadena:

1
print("My name is " + name + ", and I am " + str(age) + " years old.")

¡Y eso es! Para cadenas pequeñas, esto probablemente esté bien, pero no es muy legible. Además, es muy fácil olvidar los espacios a ambos lados de las variables que estamos concatenando. Afortunadamente, hay otras formas de construir una cuerda.

Dar formato a una cadena mediante varias declaraciones de impresión

¿Quién necesita la concatenación cuando podemos simplemente llamar a print un montón de veces?

1
2
3
4
5
print("My name is ", end="")
print(name, end="")
print(", and I am ", end="")
print(age, end="")
print(" years old.")

Ahora, sé lo que estás pensando; sí, esto solo funciona en Python 3+. Ah, y esta es una solución totalmente ridícula, pero demuestra algo importante: hay muchas formas de resolver el mismo problema.

En este caso, tomamos la  print función y aprovechamos uno de sus argumentos predeterminados ( end) para eliminar el comportamiento de nueva línea. De esa manera, podríamos encadenar un texto sin concatenación.

Una vez más, esto es definitivamente difícil de leer, y ni siquiera lo recomendaría para cadenas pequeñas. Dicho esto, elimina un tipo de elenco. Desafortunadamente, introduce mucho código duplicado.

Dar formato a una cadena con la función de unión

Continuando con nuestra búsqueda de la forma más ridícula de formatear una cadena, les traigo la  join función. Si no está familiarizado con esta función, es básicamente una forma más eficiente de concatenar cadenas. Además, nos permite proporcionar un separador para colocar entre nuestras cadenas concatenadas. Por supuesto, no lo necesitaremos:

1
print(''.join(["My name is ", name, ", and I am ", str(age), " years old"]))

Aquí, hemos llamado al  join método en una cadena de separación vacía. Como argumento, le hemos pasado una lista de cadenas. Naturalmente, join combinará esta lista de cadenas en una sola cadena sin separadores.

Por extraño que parezca, me gusta esta solución porque es sorprendentemente legible. Desafortunadamente, existen algunos inconvenientes. Por ejemplo, tenemos que convertir todas nuestras variables en cadenas de forma manual. Además, esta línea ya es bastante larga. Sin embargo, supongo que podríamos dividir todo en su propia línea.

En cualquier caso, con estos tres fuera del camino, finalmente podemos comenzar a llegar a algunas soluciones más razonables.

Dar formato a una cadena con el operador%

Ahora, estamos empezando a adentrarnos en las técnicas reales de formato de cadenas. Resulta que Python tiene su propio conjunto de herramientas de formato similares a las  printf de C:

1
print("My name is %s, and I am %d years old." % (name, age))

Aquí, hemos construido una nueva cuerda  %s reemplazada por  name y  %d reemplazada por edad.

Además de conocer  los especificadores de formato , querremos aprender la sintaxis. En particular, nuestra cadena de plantilla está seguida por el operador de módulo. Por supuesto, en este contexto, podemos llamarlo  operador de interpolación o formato de cadena  .

Luego, creamos una tupla de valores que queremos colocar en nuestra cadena. Tenga mucho cuidado de asegurar el orden de estos valores. Si están fuera de servicio, la cadena resultante puede ser incorrecta o el programa puede fallar por completo.

Con este método obtenemos una solución mucho más limpia. Por supuesto, hay trampas aquí, pero principalmente tienen que ver con cómo se asignan los valores a la cadena. Por ejemplo, debemos prestar atención a cómo ordenamos nuestros argumentos y debemos conocer nuestros especificadores de formato.

Hablando de especificadores de formato, ¿qué pasa si queremos imprimir un objeto directamente? Afortunadamente, tenemos mejores soluciones por delante.

Dar formato a una cadena con la función de formato

En lugar de usar un operador sofisticado sobrecargado, podemos hacer que nuestro código sea aún más legible usando la  format función para cadenas:

1
print("My name is {}, and I am {} years old".format(name, age))

Anteriormente, habríamos tenido que usar especificadores de formato para obtener el comportamiento que queríamos, pero ahora solo podemos usar llaves. En otras palabras, hemos eliminado un problema de la solución anterior.

Por lo que tengo entendido, este método aprovecha el  __format__ método para objetos, por lo que podemos pasar casi cualquier cosa a este método sin problemas. ¡Ahí va otro problema! Por supuesto, si la clase no tiene __str__ o  no se  __repr__ reemplaza, entonces el objeto no se imprimirá correctamente. Dicho esto, sigo contando eso como una victoria sobre la solución anterior.

Resulta que también podemos eliminar nuestro problema de pedido de la solución anterior. Todo lo que tenemos que hacer es proporcionar argumentos de palabras clave:

1
print("My name is {n}, and I am {a} years old".format(a=age, n=name))

En este ejemplo, nombramos la palabra clave age  a y la palabra clave name  nDe esa manera, podríamos colocar las palabras clave dentro de sus respectivas llaves. Para enfatizar aún más el punto, incluso podemos reordenar los argumentos sin problemas. ¡Eso es genial!

Por supuesto, debo advertirle que esta solución  puede representar una amenaza  para la seguridad de su aplicación dependiendo de cómo la esté utilizando. Si está escribiendo sus propias cadenas de formato, no debería haber ningún problema. Sin embargo, si acepta cadenas de formato de sus usuarios, es posible que desee tener cuidado.

Dar formato a una cadena usando f-Strings

Otra forma de realizar la interpolación de cadenas es utilizando la última función de cadenas f de Python (Python 3.6+). Con esta función, todo lo que tenemos que hacer es prefijar una cadena con la letra  f e insertar llaves como antes. Sin embargo, esta vez, podemos insertar el nombre de nuestras variables directamente:

1
print(f"My name is {name}, and I am {age} years old")

Eso es increíblemente elegante. Ya no tenemos que preocuparnos por:

  • Asignación de argumentos a especificadores de formato
  • Usar especificadores de formato correctamente
  • Recordando la sintaxis oscura

En su lugar, anteponemos  f y insertamos nuestras variables. ¡Eso es! Ahora, no sé si hay algún tipo de vulnerabilidad de seguridad con esta solución, pero por lo que puedo decir, no hay forma de aplicar el  f a una cadena de entrada.

En cualquier caso, eso es todo lo que tengo para soluciones de formato de cadenas. Ahora, comencemos a comparar el rendimiento de estas soluciones.

Actuación

Como siempre, me gusta configurar primero todas nuestras soluciones en cadenas:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
dieciséis
17
18
19
20
21
22
setup = """
name = "Jeremy"
age = 25
"""
concatenation = """
"My name is " + name + ", and I am " + str(age) + " years old."
"""
string_join = """
''.join(["My name is ", name, ", and I am ", str(age), " years old"])
"""
modulus = """
"My name is %s, and I am %d years old." % (name, age)
"""
format_ordered = """
"My name is {}, and I am {} years old".format(name, age)
"""
format_named = """
"My name is {n}, and I am {a} years old".format(a=age, n=name)
"""
f_string = """
f"My name is {name}, and I am {age} years old"
"""

Por mi cordura, tuve que quitar las declaraciones impresas. Como resultado, no pude probar la  print solución. Dicho esto, siéntase libre de intentarlo. Me encontré con algunos problemas con la cadena de salida que ralentizaba la prueba, e incluso intenté cambiar stdout de ruta para solucionarlo  Fue una pesadilla por decir lo menos.

En cualquier caso, es solo cuestión de llamar a nuestros  timeit comandos ahora:

01
02
03
04
05
06
07
08
09
10
11
12
13
>>> import timeit
>>> min(timeit.repeat(stmt=concatenation, setup=setup, repeat=10))
0.4947876000000022
>>> min(timeit.repeat(stmt=string_join, setup=setup, repeat=10))
0.37328679999995984
>>> min(timeit.repeat(stmt=modulus, setup=setup, repeat=10))
0.29478180000000265
>>> min(timeit.repeat(stmt=format_ordered, setup=setup, repeat=10))
0.40419490000000735
>>> min(timeit.repeat(stmt=format_named, setup=setup, repeat=10))
0.49794210000000305
>>> min(timeit.repeat(stmt=f_string, setup=setup, repeat=10))
0.1918610999999828

Como suele ocurrir con estas nuevas funciones en Python, están increíblemente optimizadas. De hecho, la única solución que incluso se acerca a competir con la solución f-String es la solución del operador de módulo.

Además, creo que vale la pena señalar cuánto más lenta es la  format función cuando los argumentos se nombran en lugar de ordenar. De hecho, es tan lento como la concatenación que esperaba que fuera horrenda. Después de todo, las cadenas son inmutables, por lo que la concatenación debería ser bastante mala.

Como siempre, tome estas métricas de rendimiento con un grano de sal.

Un pequeño resumen

Con todo lo dicho, aquí están todas las soluciones en una ubicación unificada:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
dieciséis
17
18
19
20
name = Jeremy
age = 25
# String formatting using concatenation
print("My name is " + name + ", and I am " + str(age) + " years old.")
# String formatting using multiple prints
print("My name is ", end="")
print(name, end="")
print(", and I am ", end="")
print(age, end="")
print(" years old.")
# String formatting using join
print(''.join(["My name is ", name, ", and I am ", str(age), " years old"]))
# String formatting using modulus operator
print("My name is %s, and I am %d years old." % (name, age))
# String formatting using format function with ordered parameters
print("My name is {}, and I am {} years old".format(name, age))
# String formatting using format function with named parameters
print("My name is {n}, and I am {a} years old".format(a=age, n=name))
# String formatting using f-Strings (Python 3.6+)
print(f"My name is {name}, and I am {age} years old")

Publicar un comentario

0 Comentarios