Strings en Python — teoría con una lista de clase, índices que empiezan en cero y f-strings que alinean columnas (y el strip() que no limpió nada)

Me tocó pasar a limpio la lista de clase del subgrupo. Venía de un formulario y cada uno había escrito su nombre como le había dado la gana: espacios delante, espacios detrás, mayúsculas donde no tocaban. *»Fácil: strip() para quitar los espacios y title() para las mayúsculas»*. Lo probé con el primer nombre:

# La lista de clase venía de un formulario: espacios de más y mayúsculas al azar
nombre = "   gARCÍA pérez, ANA  "

nombre.strip()          # quitar los espacios de los extremos
nombre.title()          # poner en mayúscula la primera letra de cada palabra

print(f"[{nombre}]")    # los corchetes dejan ver dónde empieza y dónde acaba

Al ejecutarlo:

[   gARCÍA pérez, ANA  ]

Ni un error, y ni un cambio. Los espacios seguían ahí y la g seguía en minúscula. «¿Entonces para qué sirven?» Sirven, pero no como yo creía: strip() y title() no cambian el string que tienen delante. Lo que hacen es devolver un string nuevo ya limpio… y yo no lo estaba guardando en ningún sitio. Python lo creaba y lo tiraba en la misma línea. Los strings en Python son inmutables: una vez creados, no cambian nunca.

Hoy entendemos los strings a fondo: cómo se escriben, qué son las secuencias de escape, cómo se llega a cada carácter con índices y slices, por qué son inmutables, cómo se cambian mayúsculas y minúsculas, cómo se busca dentro de ellos, cómo se trocean y se juntan con **split y join, y cómo se les da formato con f-strings para que las columnas salgan alineadas. El ejemplo que nos acompaña es una lista de clase desordenada**: nombres mal escritos, DNIs, fechas y notas. Si vienes de contenedores asociativos, aquí vas a ver que un string se parece mucho a una lista… hasta que intentas cambiarlo. Cuando acabes, la práctica de strings te espera con tres programas completos.

¿Qué es un string?

Un string (tipo str) es una secuencia de caracteres: letras, cifras, espacios, signos… Se escribe entre comillas, y da igual que sean dobles o simples. Lo único que importa es cerrar con las mismas que abriste.

nombre = "Ana García Pérez"                     # con comillas dobles
grupo = 'GCID-43'                               # o simples: dan exactamente lo mismo
frase = "La profe dijo: 'mañana hay control'"   # unas comillas dentro de las otras
aviso = """Entrega de la práctica:
viernes antes de las 23:59"""                   # triples: el salto de línea es parte del texto

print(frase)
print(aviso)
print(len(nombre))      # 16: los espacios también son caracteres
print(len("Núñez"))     # 5: la ñ y la ú cuentan como un carácter cada una
print(len(""))          # el string vacío existe y mide 0
print(type(grupo))

Esto es lo que imprime:

La profe dijo: 'mañana hay control'
Entrega de la práctica:
viernes antes de las 23:59
16
5
0
<class 'str'>

Vamos por partes:

  • "Ana García Pérez" y 'GCID-43' son exactamente el mismo tipo de dato. Elegir unas comillas u otras sirve para poder meter las otras dentro sin problemas, como en frase: las comillas simples de dentro no cierran el string, porque se abrió con dobles.
  • Las triples comillas (""" o ''') permiten escribir un string en varias líneas. El salto de línea que escribes al pulsar Intro forma parte del texto, y por eso aviso se imprime en dos líneas.
  • len cuenta caracteres, y los espacios también lo son. Cuéntalo tú: «Ana» (3) + espacio (1) + «García» (6) + espacio (1) + «Pérez» (5) = 16.
  • len("Núñez") da 5. La ñ y la ú son un carácter cada una, aunque en la tecla parezcan «especiales». Python trabaja con Unicode, así que una letra con tilde es una letra más.
  • "" es el string vacío: un string de verdad, que mide 0. Lo vas a ver mucho al trocear textos.

En Python no existe un tipo «carácter» aparte, como el char de Java o de C. Un carácter suelto es, simplemente, un string de longitud 1.

Secuencias de escape

Hay caracteres que no se pueden escribir tal cual dentro de las comillas: un salto de línea, un tabulador, las propias comillas… Para ellos existen las secuencias de escape, que empiezan siempre por la barra invertida \:

SecuenciaSignificaEjemploSe ve
\nSalto de línea"Hola\nAdiós"Hola (y Adiós debajo)
\tTabulador"Ana\t7.5"Ana, un hueco y 7.5
\'Comilla simple'It\'s'It’s
\"Comilla doble"Dijo \"sí\""Dijo «sí»
\\Una barra invertida"C:\\"C:\
\xhhCarácter por su código hexadecimal"\x40"@
\N{nombre}Carácter Unicode por su nombre"\N{BLACK HEART SUIT}"♥
print("Nombre\tNota")                  # \t: tabulador
print("Ana\t7.5")
print("Primera línea\nSegunda línea")   # \n: salto de línea
print('Dijo \'vale\' y se fue')         # \' : comilla simple dentro de comillas simples
print("Barra invertida: \\")            # \\ : una barra de verdad
print(len("\n"), len("\\"))             # cada secuencia de escape es UN solo carácter

ruta = "C:\notas\tema7"                 # ¡cuidado! Python ve un \n y un \t
print(ruta)
ruta_bien = r"C:\notas\tema7"           # con r delante, string "en bruto": sin escapes
print(ruta_bien)

Salida:

Nombre	Nota
Ana	7.5
Primera línea
Segunda línea
Dijo 'vale' y se fue
Barra invertida: \
1 1
C:
otas	ema7
C:\notas\tema7

Lo que conviene llevarse de aquí:

  • len("\n") y len("\\") dan 1. Aunque escribas dos caracteres en el código, la secuencia de escape es un solo carácter dentro del string.
  • El tabulador salta hasta la siguiente «parada» de la terminal. Sirve para cosas rápidas, pero si quieres columnas de verdad, alineadas pase lo que pase, usa las f-strings del final del artículo.
  • La ruta "C:\notas\tema7" es la trampa clásica de Windows. Python ve \n (salto de línea) y \t (tabulador) donde tú veías carpetas. Por eso sale «C:», un salto de línea, «otas», un tabulador y «ema7».
  • Con una **r delante** de las comillas (raw string, string «en bruto»), Python no interpreta ninguna barra. Es la forma cómoda de escribir rutas de Windows y, como verás en el tema siguiente, expresiones regulares.

Escribir rutas de Windows con barras simples y sin r. A veces «funciona» por casualidad ("C:\datos" no tiene ninguna secuencia de escape válida, aunque las versiones recientes de Python ya avisan con un SyntaxWarning) y otras se rompe en silencio. Usa r"C:\…" o barras normales /, que Python también entiende en Windows.

Índices: llegar a cada carácter

Un string es una secuencia, igual que una lista: cada carácter ocupa una posición, y la primera es la 0. Con corchetes llegas a cualquiera de ellas. Para practicarlo, usamos un DNI y una fecha de nacimiento, que tienen siempre la misma forma:

dni = "45123456D"
fecha = "03/09/2007"        # día/mes/año, siempre con dos cifras

print(dni[0], dni[8])       # primer y último carácter: posiciones 0 y 8
print(dni[-1])              # -1 es el último, sin tener que contar
print(dni[:8])              # del principio hasta la 8, SIN incluirla: las cifras
print(fecha[3:5])           # posiciones 3 y 4: el mes
print(fecha[-4:])           # los 4 últimos caracteres: el año
print(fecha[:2], fecha[3:5], fecha[6:])   # día, mes y año por separado
print(dni[::2])             # de dos en dos: posiciones 0, 2, 4, 6 y 8
print(fecha[::-1])          # paso -1: el string al revés
print(dni[5:50])            # un slice que se pasa del final NO da error

Y su salida:

4 D
D
45123456
09
2007
03 09 2007
4135D
7002/90/30
456D

Fíjate primero en el dibujo, porque es la clave de todo el apartado:

Índices y slices de un stringLos diez caracteres de "03/09/2007" en casillas, con sus índices de 0 a 9 encima y de -10 a -1 debajo. fecha[3:5] corta entre las posiciones 3 y 5 y da "09"; fecha[-4:] toma los cuatro últimos y da "2007".Índices y slices de un stringfecha = "03/09/2007"índiceíndice negativo00-1031-9/2-803-794-6/5-526-407-308-279-135fecha[3:5] → "09"fecha[-4:] → "2007"← los cortes van ENTRE caracteres
Cada carácter tiene dos índices; un slice [a:b] corta en las rayas a y b, así que la posición b se queda fuera.

Cada carácter tiene dos índices: el normal, que cuenta desde el principio empezando en 0, y el negativo, que cuenta desde el final empezando en -1. En "03/09/2007" el último carácter es a la vez fecha[9] y fecha[-1]. Los negativos son muy cómodos para «lo último» sin saber cuánto mide el string.

Ahora, los slices (o segmentos). cadena[a:b] devuelve un string nuevo con los caracteres desde la posición a **hasta la b sin incluirla. La forma más fácil de no equivocarse es la del dibujo: imagina que los índices son rayas entre los caracteres**. fecha[3:5] corta en la raya 3 y en la raya 5, y lo que queda en medio es "09", el mes.

Línea a línea:

ExpresiónQué tomaResultado
dni[0], dni[8]Las posiciones 0 y 84 y D
dni[-1]El último carácterD
dni[:8]Desde el principio hasta la 8, sin ella45123456
fecha[3:5]Posiciones 3 y 409
fecha[-4:]Desde la cuarta por el final hasta el final2007
fecha[:2], fecha[3:5], fecha[6:]Día, mes y año03, 09, 2007
dni[::2]Posiciones 0, 2, 4, 6 y 8 (paso 2)4135D
fecha[::-1]Todo, al revés (paso -1)7002/90/30
dni[5:50]De la 5 hasta «la 50», que no existe456D

Tres detalles que suelen pillar:

  • Si omites el primer número, el slice empieza al principio; si omites el segundo, llega hasta el final. fecha[:2] + fecha[2:] es siempre la fecha entera: el corte en la raya 2 la parte en dos sin perder nada.
  • Un tercer número es el paso. Con ::2 vas de dos en dos, y con ::-1 recorres el string hacia atrás. Es la forma típica de darle la vuelta a un string.
  • Un slice que se pasa del final no da error: devuelve lo que haya. Un índice suelto que se pasa, sí:
dni = "45123456D"
print(dni[9])     # el DNI tiene 9 caracteres: sus posiciones van de 0 a 8

Al ejecutarlo:

Traceback (most recent call last):
  File "error_indice.py", line 2, in <module>
    print(dni[9])     # el DNI tiene 9 caracteres: sus posiciones van de 0 a 8
          ~~~^^^
IndexError: string index out of range

Pensar que len(s) es la última posición. Si el DNI mide 9, sus posiciones van de 0 a 8, y dni[9] no existe. El último carácter es dni[len(dni) - 1], que se escribe mucho más cómodo como dni[-1].

Cuando dudes con un slice, cuenta las rayas, no las casillas. [3:5] son 5 − 3 = 2 caracteres, siempre.

Los strings son inmutables

Aquí está el fallo de mi lista. Una lista se puede cambiar por dentro: notas[0] = 10 sustituye el primer elemento. Un string no. Si lo intentas, Python te para en seco:

nombre = "ana"
nombre[0] = "A"     # intentar cambiar la primera letra "en su sitio"

Esto es lo que imprime:

Traceback (most recent call last):
  File "error_asignar.py", line 2, in <module>
    nombre[0] = "A"     # intentar cambiar la primera letra "en su sitio"
    ~~~~~~^^^
TypeError: 'str' object does not support item assignment

«Does not support item assignment»: un string no admite que asignes a una de sus posiciones. Y lo mismo pasa con todos sus métodos: ninguno cambia el string original. strip, title, upper, replace… todos devuelven un string nuevo. Si no lo guardas, se pierde:

nombre = "   gARCÍA pérez, ANA  "

limpio = nombre.strip()          # strip DEVUELVE un string nuevo: hay que guardarlo
bonito = limpio.title()          # y title devuelve otro más
print(f"[{nombre}]")             # el original sigue intacto
print(f"[{limpio}]")
print(f"[{bonito}]")

nombre = nombre.strip().title()  # lo habitual: reasignar la misma variable
print(f"[{nombre}]")             # title trabaja sobre lo que devuelve strip

print(nombre[0])                 # LEER un carácter sí se puede

Salida:

[   gARCÍA pérez, ANA  ]
[gARCÍA pérez, ANA]
[García Pérez, Ana]
[García Pérez, Ana]
G
Los strings no cambian: se crean nuevosLa variable nombre apunta al string original con espacios. strip() crea un string nuevo sin espacios y title() otro con mayúsculas correctas. Al reasignar, la flecha de nombre pasa al último; el original queda sin nadie que lo apunte.Los strings no cambian: se crean nuevosnombre = nombre.strip().title()nombre" gARCÍA pérez, ANA "original: intacto"gARCÍA pérez, ANA"nadie lo guarda"García Pérez, Ana"strip()title()antesdespués
Ningún método modifica el string original: devuelven otro, y solo la asignación hace que la variable apunte al nuevo.

Sigue el dibujo de arriba abajo:

  • nombre.strip() crea un string nuevo sin los espacios de los extremos. Como esta vez sí lo guardamos en limpio, no se pierde. El original sigue intacto: la primera línea de la salida lo demuestra.
  • limpio.title() crea otro string, con la primera letra de cada palabra en mayúscula y el resto en minúscula: "García Pérez, Ana".
  • La línea nombre = nombre.strip().title() es la forma habitual de trabajar. Se lee de izquierda a derecha: strip() devuelve un string nuevo, title() se aplica sobre ese nuevo y devuelve otro, y la asignación hace que nombre apunte al último. El string del principio no ha cambiado: simplemente, ya nadie lo apunta.
  • nombre[0] funciona sin problemas: leer una posición sí se puede. Lo que no se puede es escribir en ella.

Llamar a un método de string en una línea suelta (nombre.strip()) y esperar que la variable cambie. No da error, así que el fallo pasa desapercibido. Si el método devuelve un string, la línea tiene que tener un = delante.

¿y si de verdad necesito cambiar una letra? Se construye un string nuevo con trozos del viejo: "A" + nombre[1:] cambia la primera letra. Y si vas a hacer muchos cambios carácter a carácter, a veces compensa pasar a lista (list(texto)), cambiar la lista y volver a juntarla con "".join(…).

Mayúsculas y minúsculas

Los métodos de mayúsculas son los que más vas a usar para limpiar datos escritos por personas:

texto = "maría josé o'neill de la fuente"

print(texto.upper())            # todo en mayúsculas (también la í y la é)
print(texto.capitalize())       # solo la primera letra del string
print(texto.title())            # la primera letra de cada palabra
print("it's fp1".title())       # title no sabe de apóstrofos ni de siglas
print("FP1 Es Fácil".swapcase())        # invierte mayúsculas y minúsculas
print(texto.islower(), texto.isupper())

# Comparar sin que importen las mayúsculas: se pasan los dos lados a minúsculas
a = "GARCÍA"
b = "García"
print(a == b)                   # False: para Python son caracteres distintos
print(a.lower() == b.lower())   # True
print("Straße".lower(), "Straße".casefold())   # casefold va más allá que lower

Y su salida:

MARÍA JOSÉ O'NEILL DE LA FUENTE
María josé o'neill de la fuente
María José O'Neill De La Fuente
It'S Fp1
fp1 eS fÁCIL
True False
False
True
straße strasse
MétodoHaceOjo con
upper()Todo a mayúsculasFunciona con tildes y ñ: "josé".upper() es "JOSÉ"
lower()Todo a minúsculas
capitalize()Primera letra del string en mayúscula y el resto en minúsculaPor eso «josé» sale en minúscula en la segunda línea
title()Primera letra de cada palabra en mayúsculaPone «De La» y no entiende de apóstrofos: "it's" → "It'S"
swapcase()Invierte mayúsculas y minúsculas
isupper(), islower(), istitle()Preguntan, devuelven True/FalseNo cambian nada
casefold()Como lower(), pero más agresivoÚtil para comparar textos en cualquier idioma

Dos cosas importantes del programa:

  • title() considera «palabra» todo lo que va después de algo que no es una letra. Por eso "o'neill" queda bien ("O'Neill"), pero "it's" queda mal ("It'S") y "fp1" sale "Fp1". Y las partículas de los apellidos, «de la», salen en mayúscula: "De La Fuente". En la práctica arreglaremos eso a mano.
  • Para Python, "GARCÍA" y "García" son strings distintos: cada letra tiene su propio código y la A no es la a. Para comparar sin que importen las mayúsculas, se pasan los dos lados a minúsculas (o a mayúsculas) antes de comparar.

Si comparas textos de cualquier idioma, casefold() es más seguro que lower(). La ß alemana es el ejemplo de manual: lower() la deja como está y casefold() la convierte en ss, que es como se escribe en mayúsculas.

Categorías de caracteres: ¿qué tiene dentro?

Antes de convertir un string a número, o de dar por bueno un DNI, conviene preguntar qué clase de caracteres contiene. Para eso están los métodos is…, que devuelven True solo si todos los caracteres cumplen la condición (y el string no está vacío):

pruebas = ["45123456", "Ana", "Ana Pérez", "7,5", "GCID43", "   ", ""]

for t in pruebas:
    # repr() muestra las comillas: así se ven los espacios y el string vacío
    print(repr(t), t.isdigit(), t.isalpha(), t.isalnum(), t.isspace())

print("²".isdigit(), "²".isdecimal())                 # un superíndice es dígito, pero no decimal
print("nota_final".isidentifier(), "2nota".isidentifier())   # ¿valdría como nombre de variable?

Al ejecutarlo:

'45123456' True False True False
'Ana' False True True False
'Ana Pérez' False False False False
'7,5' False False False False
'GCID43' False False True False
'   ' False False False True
'' False False False False
True False
True False

Lo mismo, en una tabla para leerlo mejor:

Textoisdigit()isalpha()isalnum()isspace()
'45123456'TrueFalseTrueFalse
'Ana'FalseTrueTrueFalse
'Ana Pérez'FalseFalseFalseFalse
'7,5'FalseFalseFalseFalse
'GCID43'FalseFalseTrueFalse
' 'FalseFalseFalseTrue
''FalseFalseFalseFalse
  • "Ana Pérez" no es isalpha() por el espacio: el espacio no es una letra. Si quieres comprobar un nombre completo, tendrás que mirar palabra a palabra.
  • "7,5" no es isdigit(), por la coma. isdigit() dice si todo son cifras, no si «parece un número». Por eso no sirve para comprobar decimales ni negativos ("-3" también da False).
  • El string vacío da False en todo. Es un detalle que te protege: un campo vacío del formulario nunca pasará por un número.
  • isalnum() es «letras o cifras»: "GCID43" lo cumple.
  • "²" es isdigit() pero no isdecimal(). isdecimal() es el más estricto de los tres métodos de cifras (isdecimal, isdigit, isnumeric), y el que deberías usar antes de hacer int(texto).
  • isidentifier() dice si el texto valdría como nombre de variable: "nota_final" sí, "2nota" no, porque empieza por cifra.

Concatenar y repetir

Con strings, + une (concatena) y * repite:

nombre = "Ana"
apellidos = "García Pérez"
completo = nombre + " " + apellidos   # + no añade espacios: hay que ponerlos
print(completo)
print("-" * 20)                       # * repite: una línea separadora
print("ja" * 3)

nota = 7.5
print("Nota: " + str(nota))           # + solo une strings: el número se convierte con str()
print("Nota:", nota)                  # print con comas acepta números y pone el espacio

firma = ""                            # acumular en un bucle
for parte in ["Ana", "García", "Pérez"]:
    firma += parte[0]                 # += crea un string nuevo en cada vuelta
print(firma)

Esto es lo que imprime:

Ana García Pérez
--------------------
jajaja
Nota: 7.5
Nota: 7.5
AGP
  • + junta los strings tal cual, sin añadir nada en medio. Si quieres un espacio entre el nombre y los apellidos, tienes que sumarlo tú: nombre + " " + apellidos.
  • "-" * 20 es una forma rápida de dibujar una línea separadora de 20 guiones. Es a la concatenación lo que la multiplicación a la suma.
  • La última parte acumula en un bucle: firma empieza vacía y en cada vuelta se le suma la primera letra de una palabra. Cada += crea un string nuevo (los strings no cambian, ¿recuerdas?) y firma pasa a apuntarlo. Vuelta a vuelta:
Vueltaparteparte[0]firma después
1"Ana""A""A"
2"García""G""AG"
3"Pérez""P""AGP"

El error más frecuente de este apartado es sumar un string y un número:

nota = 7.5
print("Nota: " + nota)     # un string más un float

Salida:

Traceback (most recent call last):
  File "error_concat.py", line 2, in <module>
    print("Nota: " + nota)     # un string más un float
          ~~~~~~~~~^~~~~~
TypeError: can only concatenate str (not "float") to str

"Nota: " + nota con nota numérica. + no convierte nada por su cuenta: o conviertes tú con str(nota), o usas print con comas, o (lo mejor) usas una f-string: f"Nota: {nota}".

Buscar dentro de un string

Para buscar trozos (substrings) dentro de un string hay varias herramientas. Las probamos con un correo de la universidad:

correo = "ana.garcia@alu.ulpgc.es"

print("@" in correo)                  # ¿está? True o False
print(correo.find("@"))               # posición de la primera aparición
print(correo.find("#"))               # -1 si no está, sin error
print(correo.find("a"), correo.rfind("a"))      # la primera "a" y la última
print(correo.count("."))              # cuántas veces aparece
print(correo.startswith("ana"), correo.endswith("ulpgc.es"))

usuario, arroba, dominio = correo.partition("@")   # 3 trozos, cortando por la primera "@"
print(usuario, "|", arroba, "|", dominio)
print(correo[:correo.find("@")])      # lo mismo que usuario, con find y un slice
print(correo.index("@"))              # como find... mientras lo encuentre

Y su salida:

True
10
-1
0 11
3
True True
ana.garcia | @ | alu.ulpgc.es
ana.garcia
10

Comprobemos los números contando a mano en "ana.garcia@alu.ulpgc.es": a(0) n(1) a(2) .(3) g(4) a(5) r(6) c(7) i(8) a(9) @(10) a(11)…

  • "@" in correo responde solo sí o no. Si no necesitas la posición, es lo más claro.
  • find("@") devuelve la posición de la primera aparición: la 10. Si no lo encuentra, devuelve -1, sin error.
  • find("a") encuentra la primera a, en la posición 0, y rfind("a") (la r es de right, desde la derecha) la última, en la 11.
  • count(".") cuenta las apariciones: hay 3 puntos (uno en el usuario y dos en el dominio).
  • startswith y endswith comprueban el principio y el final. Son perfectos para validar: «¿es un correo de alumno de la ULPGC?».
  • partition("@") corta por la primera @ y devuelve una tupla de tres: lo de antes, el separador y lo de después. Siempre devuelve tres trozos, así que se puede desempaquetar sin miedo.
  • correo[:correo.find("@")] hace lo mismo que el usuario de partition, combinando find y un slice: desde el principio hasta la posición de la @, sin incluirla.

index hace lo mismo que find… hasta que no encuentra lo que busca:

correo = "ana.garcia@alu.ulpgc.es"
print(correo.index("#"))     # index no devuelve -1: si no lo encuentra, error

Al ejecutarlo:

Traceback (most recent call last):
  File "error_index.py", line 2, in <module>
    print(correo.index("#"))     # index no devuelve -1: si no lo encuentra, error
          ~~~~~~~~~~~~^^^^^
ValueError: substring not found

¿find, index o in?

SituaciónUsaPor qué
Solo quieres saber si está"@" in correoDevuelve True/False y se lee solo
Necesitas la posición y puede no estarfindDevuelve -1, y tú decides qué hacer
Necesitas la posición y tiene que estarindexSi no está, el ValueError te avisa de un fallo de verdad
Quieres partir en «antes» y «después»partitionTres trozos de una vez, sin calcular posiciones

Usar el resultado de find sin comprobarlo. Si devuelve -1 y haces correo[:-1], no sale ningún error: sale el correo entero sin el último carácter. Antes de usar la posición, comprueba que no es -1.

Trocear y juntar: split, join, replace y strip

Este es el apartado que más vas a usar en los ejercicios de la asignatura: los datos llegan como una línea de texto y hay que convertirla en trozos útiles. Mira esta línea, copiada de una hoja de cálculo con espacios por todas partes:

linea = "  García Pérez, Ana ;45123456D;  03/09/2007 ; 7,5 "

partes = linea.split(";")                    # corta por cada ";" y devuelve una LISTA
print(partes)
limpias = [p.strip() for p in partes]        # strip a cada trozo
print(limpias)

apellidos, nombre = limpias[0].split(", ")   # "García Pérez" y "Ana"
nota = float(limpias[3].replace(",", "."))   # float no entiende la coma decimal
print(nombre, apellidos, nota)

print("Ana  García   Pérez".split())         # sin argumento: corta por cualquier hueco
print("Ana  García".split(" "))              # con " ": cada espacio es un corte
print("03/09/2007".split("/", 1))            # como mucho 1 corte

print(" ".join(["Ana", "García", "Pérez"]))  # el separador es el string de delante del punto
print("/".join(["03", "09", "2007"]))
print("-".join("FP1"))                       # un string también es una secuencia
print(linea.replace(";", " | ").strip())     # replace cambia TODAS las apariciones

Esto es lo que imprime:

['  García Pérez, Ana ', '45123456D', '  03/09/2007 ', ' 7,5 ']
['García Pérez, Ana', '45123456D', '03/09/2007', '7,5']
Ana García Pérez 7.5
['Ana', 'García', 'Pérez']
['Ana', '', 'García']
['03', '09/2007']
Ana García Pérez
03/09/2007
F-P-1
García Pérez, Ana  | 45123456D |   03/09/2007  |  7,5

Paso a paso:

  1. linea.split(";") corta por cada ; y devuelve una lista de strings. El ; desaparece, pero los espacios que había alrededor se quedan pegados a los trozos: ' García Pérez, Ana ', ' 03/09/2007 '…
  2. Por eso el siguiente paso casi siempre es **strip() a cada trozo**. La list comprehension lo hace en una línea: [p.strip() for p in partes].
  3. limpias[0].split(", ") parte el primer campo por la coma seguida de espacio, y como da exactamente dos trozos, se desempaquetan en apellidos y nombre.
  4. float("7,5") fallaría, porque Python solo entiende el punto decimal. replace(",", ".") devuelve "7.5" y ese sí se convierte.

Luego vienen las tres formas de usar split, que no son lo mismo:

LlamadaCorta porCon "Ana García" (dos espacios)
split()Cualquier hueco (espacios, tabuladores, saltos de línea), por grande que sea['Ana', 'García']
split(" ")Cada espacio, uno a uno['Ana', '', 'García'] ← trozo vacío
split("/", 1)Por /, pero como mucho 1 vez['03', '09/2007']

Usar split(" ") para separar palabras. Con dos espacios seguidos aparece un string vacío en medio, y en cuanto hagas trozo[0] sobre él salta un IndexError. Para palabras, split() sin argumento.

join es la operación contraria a split: recibe una secuencia de strings y los une, poniendo entre ellos el string sobre el que se llama. Se escribe «al revés» de como uno espera: el separador va delante del punto.

  • " ".join(["Ana", "García", "Pérez"]) → "Ana García Pérez".
  • "/".join(["03", "09", "2007"]) vuelve a montar la fecha.
  • "-".join("FP1") → "F-P-1": un string también es una secuencia (de caracteres), así que join pone el guion entre cada uno.

" ".join(texto.split()) es la forma más corta de normalizar espacios: quita los de los extremos y deja uno solo entre palabras. split() se come todos los huecos y join vuelve a pegar las palabras con un único espacio. La usaremos en la práctica.

Por último, replace(viejo, nuevo) devuelve una copia con todas las apariciones cambiadas (si solo quieres cambiar las primeras, admite un tercer argumento con cuántas).

strip y sus primos

strip() quita los espaciadores de los dos extremos; lstrip(), solo los de la izquierda (left), y rstrip(), solo los de la derecha (right). Nunca toca los espacios del medio. Y si le pasas un argumento, quita esos caracteres en lugar de espacios:

codigo = "  FP1  "
print(f"[{codigo.strip()}] [{codigo.lstrip()}] [{codigo.rstrip()}]")
print("--==FP1==--".strip("-="))     # con argumento: quita esos caracteres de los extremos

nombre = "Ana"
print(f"[{nombre.ljust(10)}]")       # a la izquierda en 10 caracteres
print(f"[{nombre.rjust(10, '.')}]")  # a la derecha, rellenando con puntos
print(f"[{nombre.center(11, '*')}]") # centrado
print("7".zfill(3), "43".zfill(3))   # ceros a la izquierda hasta 3 cifras

Salida:

[FP1] [FP1  ] [  FP1]
FP1
[Ana       ]
[.......Ana]
[****Ana****]
007 043

La segunda mitad del programa hace lo contrario: rellena hasta un ancho. ljust(10) deja "Ana" a la izquierda en 10 caracteres (3 de texto y 7 espacios), rjust(10, '.') lo pone a la derecha rellenando con puntos, y center(11, '*') reparte el hueco: 11 − 3 = 8 asteriscos, 4 a cada lado. zfill(3) añade ceros a la izquierda hasta llegar a 3 cifras: muy útil para números de lista (007) o códigos.

Formatear con f-strings

Desde Python 3.6, la forma preferida de construir textos con datos dentro son las f-strings: un string con una f delante en el que puedes meter cualquier expresión entre llaves. Python la evalúa, la convierte a texto y la coloca en su sitio.

nombre = "Ana García"
nota = 7.456
num = 7

print(f"{nombre} ha sacado un {nota}")      # entre llaves va cualquier expresión
print(f"Con un punto más: {nota + 1}")
print(f"Con 2 decimales: {nota:.2f}")       # .2f: 2 decimales fijos
print(f"Con 2 cifras: {nota:.2}")           # ¡ojo! sin la f son 2 cifras significativas
print(f"[{nombre:15}] [{nota:8.2f}]")       # ancho mínimo: texto a la izquierda, números a la derecha
print(f"[{nombre:>15}] [{nombre:^15}]")     # > derecha, ^ centro (< izquierda)
print(f"[{nombre:.^16}]")                   # relleno con puntos y centrado
print(f"N.º de lista: {num:03d}")           # ceros a la izquierda hasta 3 cifras
print(f"{num:+d} {-num:+d}")                # el signo siempre, también el +
print(f"{255:x} {255:X} {8:o} {1234.5:e}")  # hexadecimal, octal y notación científica
print(f"{0.856:.1%}")                       # porcentaje con 1 decimal
print(f"Las llaves se doblan: {{nota}}")
print("{} tiene un {:.1f}".format(nombre, nota))   # el estilo antiguo, con format()

Y su salida:

Ana García ha sacado un 7.456
Con un punto más: 8.456
Con 2 decimales: 7.46
Con 2 cifras: 7.5
[Ana García     ] [    7.46]
[     Ana García] [  Ana García   ]
[...Ana García...]
N.º de lista: 007
+7 -7
ff FF 10 1.234500e+03
85.6%
Las llaves se doblan: {nota}
Ana García tiene un 7.5

Lo básico está en las dos primeras líneas: {nombre} y {nota} se sustituyen por su valor, y {nota + 1} demuestra que entre llaves puede ir una expresión entera, no solo una variable (7.456 + 1 = 8.456).

Lo potente viene después de los dos puntos: el modificador de formato, que no cambia el valor, sino cómo se escribe. Fíjate en las partes de uno completo:

Anatomía de {nota:>8.2f}La f-string {nota:>8.2f} dividida en partes: la expresión nota, los dos puntos, la alineación >, el ancho 8, la precisión .2 y el tipo f. Con nota = 7.456 el resultado ocupa 8 casillas: cuatro espacios y 7.46 pegado a la derecha.Anatomía de {nota:>8.2f}{nota:>8.2f}la expresiónempieza el formatoa la derechaancho mínimoprecisióndecimal fijoresultado: 8 caracteres␣␣␣␣7.46← 7.456
Lo que va después de los dos puntos no cambia el valor: solo decide cuánto sitio ocupa y cómo se escribe.
ModificadorSignificaEjemploResultado
:15Ancho mínimo de 15f"[{nombre:15}]"[Ana García ]
:<15, :>15, :^15Alinear a la izquierda, derecha o centrof"[{nombre:>15}]"[ Ana García]
:.^16Rellenar con un carácter (aquí, puntos)f"[{nombre:.^16}]"[...Ana García...]
:.2f2 decimales fijosf"{7.456:.2f}"7.46
:8.2fAncho 8 y 2 decimalesf"[{7.456:8.2f}]"[ 7.46]
:.22 cifras significativasf"{7.456:.2}"7.5
:03dEntero con ceros a la izquierda hasta 3f"{7:03d}"007
:+dMostrar siempre el signof"{7:+d}"+7
:x, :X, :oHexadecimal, en mayúsculas, octalf"{255:x}"ff
:eNotación científicaf"{1234.5:e}"1.234500e+03
:.1%Porcentaje con 1 decimalf"{0.856:.1%}"85.6%

Comprueba las cuentas de la salida:

  • [Ana García ]: «Ana García» mide 10, y el ancho 15 añade 5 espacios. Un texto se alinea a la izquierda por defecto.
  • [ 7.46]: «7.46» mide 4, y el ancho 8 añade 4 espacios a la izquierda. Un número se alinea a la derecha por defecto, que es justo lo que quieres en una columna de notas.
  • [ Ana García ]: centrado en 15 sobran 5 espacios, que no se pueden repartir a partes iguales; Python pone 2 a la izquierda y 3 a la derecha.
  • 0.856 como porcentaje es 85.6 %: el formato % multiplica por 100 y añade el símbolo.
  • Para escribir una llave de verdad dentro de una f-string, se dobla: {{nota}} se imprime como {nota}.
  • La última línea es el método format(), el estilo anterior a las f-strings: las llaves vacías se rellenan, en orden, con los argumentos. Lo verás en código antiguo y en los apuntes; para código nuevo, f-strings.

Escribir :.2 queriendo dos decimales. Sin la f, el número indica cifras significativas, no decimales: con 7.456 da 7.5 y con 10.0 da 1e+01. Para decimales, siempre :.2f.

Columnas alineadas

El gran uso del ancho de campo es imprimir tablas que se lean bien. El truco es usar los mismos anchos en la cabecera y en cada fila:

notas = {"Ana García": 7.456, "Luis Romero": 10, "Marta Gil": 4.95}

print(f"{'Alumno':<14}{'Nota':>6}")      # la cabecera con los mismos anchos que las filas
print("-" * 20)                          # 14 + 6 = 20
for alumno, nota in notas.items():
    print(f"{alumno:<14}{nota:>6.2f}")   # nombre a la izquierda en 14; nota a la derecha en 6

Al ejecutarlo:

Alumno          Nota
--------------------
Ana García      7.46
Luis Romero    10.00
Marta Gil       4.95
  • {alumno:<14} reserva 14 caracteres para el nombre, a la izquierda. «Luis Romero» mide 11, así que le siguen 3 espacios; «Marta Gil», 9, le siguen 5. El nombre es distinto en cada fila, pero la columna siguiente empieza siempre en el carácter 15.
  • {nota:>6.2f} escribe la nota con 2 decimales, a la derecha en 6. Las notas quedan alineadas por el punto decimal: 7.46, 10.00 y 4.95 terminan en la misma columna. Y 10 (un entero en el diccionario) sale como 10.00, porque f lo trata como decimal.
  • La línea de guiones mide 14 + 6 = 20, el ancho total de la tabla.

Si una columna se te «descoloca», el problema casi siempre es que la cabecera y las filas usan anchos distintos, o que un valor es más largo que su ancho (el ancho es un mínimo: si no cabe, Python no recorta, ocupa lo que necesite).

Míralo paso a paso en Python Tutor

Python Tutor ejecuta el código línea a línea y dibuja cada variable con su valor. Con los strings es muy útil para ver que los métodos no cambian la variable si no hay asignación. Elige Python 3 y pega este programa:

nombre = "  ana garcía  "
nombre.strip()                    # devuelve un string nuevo... que nadie guarda
limpio = nombre.strip()           # ahora sí lo guardamos
bonito = limpio.title()
partes = bonito.split()           # una lista con dos strings
iniciales = partes[0][0] + partes[1][0]
print(f"[{nombre}]")
print(f"[{bonito}]", partes, iniciales)

Salida esperada:

[  ana garcía  ]
[Ana García] ['Ana', 'García'] AG

Qué observar, paso a paso:

  1. Después de la línea 2, mira el panel de variables: nombre sigue teniendo los espacios. strip() ha creado un string limpio, pero como nadie lo ha guardado, no aparece en ningún sitio.
  2. En la línea 3 aparece por fin limpio, sin espacios. nombre no ha cambiado: ahora hay dos variables con dos strings distintos.
  3. En la línea 5, partes se dibuja como una lista con dos strings dentro. Python Tutor dibuja las listas aparte, con una flecha, porque son objetos que pueden cambiar; los strings los suele escribir directamente al lado de la variable.
  4. En la línea 6, partes[0][0] se lee en dos pasos: partes[0] es "Ana" y [0] sobre ese string es "A". Lo mismo con partes[1][0], que da "G".
  5. Al final, la salida confirma lo del paso 1: nombre se imprime con todos sus espacios.

Resumen

ConceptoLo esencial
StringSecuencia de caracteres entre comillas (simples, dobles o triples)
len(s)Número de caracteres; los espacios cuentan
Escapes\n, \t, \', \\… cuentan como un carácter; r"…" los desactiva
Índicess[0] el primero, s[-1] el último; fuera de rango → IndexError
Slicess[a:b] de a hasta b sin incluirla; s[::-1] al revés; nunca dan error
InmutablesNingún método cambia el string: devuelven uno nuevo que hay que guardar
Mayúsculasupper, lower, capitalize, title, swapcase; comparar con lower() en los dos lados
Categoríasisdigit, isalpha, isalnum, isspace… False con el string vacío
+ y *Concatenar y repetir; + solo une strings (usa str() o f-strings)
Buscarin, find (-1), index (error), rfind, count, startswith, endswith, partition
Trocear y juntarsplit() por huecos, split(sep) por cada separador, sep.join(lista)
Limpiar y rellenarstrip, lstrip, rstrip, replace, ljust, rjust, center, zfill
f-stringsf"{expr:formato}": ancho, alineación < > ^, .2f, 03d, %

Tres ideas para llevarte:

  1. Los strings no cambian nunca. Cada método devuelve uno nuevo: si la línea no tiene un =, el resultado se pierde.
  2. Los índices son rayas entre caracteres. s[a:b] corta en la raya a y en la b, mide b − a y nunca incluye la posición b.
  3. **Para separar palabras, split() sin argumento; para columnas, f-strings con el mismo ancho en cabecera y filas.** Con eso tienes resuelta la mitad de los ejercicios de texto de la asignatura.

En la práctica de strings aplicarás todo esto a una lista de clase de verdad: limpiar nombres escritos de cualquier manera y generar usuarios, validar DNIs y fechas, y sacar un acta con las columnas alineadas. Después, los ejercicios te pondrán a prueba. Y en el siguiente tema, las expresiones regulares: buscar patrones (como «8 cifras y una letra») en lugar de textos exactos.

Siguiente paso: prácticaStrings en Python — práctica limpiando una lista de clase, validando DNIs y sacando un acta alineada (y el 7,5 que float() no quiso leer)

¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

Sigue aprendiendo

3 comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *