1.9 · Cadenas¶
Objetivos. Al terminar este capítulo podrás: acceder y segmentar cadenas, transformarlas (mayúsculas, limpieza, reemplazo), dividirlas y unirlas, validarlas y formatearlas; y usar lo básico de expresiones regulares.
Evidencia de logro. Escribirás un analizador de texto que normalice, cuente palabras y extraiga números con regex, y un validador de entradas.
Contexto y motivación¶
El texto aparece en muchos contextos de IA: reseñas, prompts, respuestas, nombres de contactos y mensajes de log. Muchas entradas de una API o de un formulario llegan como cadenas. Python las trata como secuencias inmutables: cada operación que parece modificar una cadena crea otra nueva. Por eso conviene conocer los métodos que permiten procesar texto.
Vocabulario¶
| Término | Significado |
|---|---|
Cadena (str) |
Secuencia inmutable de caracteres |
| Slicing | Extraer un trozo [inicio:fin] |
| Inmutable | No se puede modificar; cada cambio crea una nueva |
| Método | Función asociada a un objeto (s.upper()) |
| Normalizar | Limpiar un texto (minúsculas, sin espacios) para comparar |
| Regex | Patrón para buscar texto (re) |
Prerrequisitos¶
1.6 · Listas y tuplas (por el split que devuelve
listas) y operadores.
1. Acceso y slicing¶
frase = "Python es divertido"
print(frase[0]) # primer carácter
print(frase[-1]) # último
print(frase[0:6]) # Python
print(frase[::-1]) # invertida
print(len(frase)) # número de caracteres (los espacios cuentan)
Salida esperada:
Las cadenas se indexan igual que las listas; el slicing usa [inicio:fin]. Como
son inmutables, no se puede cambiar un carácter; para "cambiar" una cadena
creas otra nueva:
Salida esperada (error):
2. Mayúsculas y minúsculas¶
print("hola mundo".upper()) # todo en mayúsculas
print("HOLA".lower()) # todo en minúsculas
print("ana lopez".title()) # cada palabra con inicial mayúscula
Salida esperada:
lower() es la herramienta básica de normalización: permite comparar
correos, nombres o claves sin que importen las mayúsculas
("Admin".lower() == "admin" es True). Ninguno de estos métodos cambia la
cadena original: devuelven una cadena nueva.
3. Búsqueda y comprobación¶
frase = "Python es divertido"
print(frase.find("es")) # posición de la primera aparición
print(frase.count("i")) # cuántas veces aparece
print(frase.startswith("Py")) # ¿empieza por?
print(frase.endswith("ido")) # ¿termina en?
Salida esperada:
find devuelve la posición o -1 si no está. count, startswith y endswith
sirven para validar y filtrar.
4. Limpiar y reemplazar¶
print(" hola ".strip()) # quitar espacios
print("a,b,c".replace(",", "-")) # reemplazar
print("Python es divertido".replace("Python", "Julia"))
Salida esperada:
strip() limpia espacios al inicio/final (clave con entradas de usuario);
replace(a, b) sustituye. Para limpiar también mayúsculas y espacios, combina
texto.strip().lower(): es el patrón de normalización típico. Los métodos se
pueden encadenar porque cada uno devuelve una cadena nueva, sobre la que
actúa el siguiente.
5. Dividir y unir¶
frase = "Python es divertido"
print(frase.split()) # separa por espacios
print("-".join(["a", "b", "c"])) # une con guion
print("a,b,c".split(",")) # separa por coma
Salida esperada:
split() devuelve una lista; join() hace lo contrario. Son las dos
operaciones más usadas para pasar de texto a estructura y viceversa.
6. Validación de cadenas¶
print("123".isdigit()) # ¿solo dígitos?
print("abc".isalpha()) # ¿solo letras?
print(" ".isspace()) # ¿solo espacios?
print("a1".isalnum()) # ¿letras o dígitos?
Salida esperada:
Con isdigit() puedes validar antes de convertir con int(), evitando los
ValueError que verás en el capítulo de excepciones. Ojo con sus límites:
"-5".isdigit() y "3.5".isdigit() dan False, porque el signo y el punto no
son dígitos. Para números con signo o decimales, la forma robusta es intentar la
conversión y capturar el error (1.11).
7. Formateo¶
El f-string es la forma moderna y más legible:
nombre = "Ana"
edad = 20
print(f"{nombre} tiene {edad} años")
print("Hola {}".format(nombre)) # método .format()
Salida esperada:
f"..." permite meter variables entre llaves. Entre {} puedes formatear
con ::
precio = 1234.567
print(f"{precio:.2f}") # 2 decimales
print(f"{precio:,.2f}") # separador de miles
print(f"{precio:>10}") # alineado a la derecha (ancho 10)
print(f"|{precio:<10.2f}|") # a la izquierda, 2 decimales; | marca el ancho
print(f"{precio:010.2f}") # rellena con ceros a la izquierda
Salida esperada:
Cómo leer cada línea: .2f redondea al mostrar a dos decimales; , añade
separador de miles al estilo inglés (coma para miles y punto para decimales, no
el formato español 1.234,57); >10 reserva 10 caracteres y alinea a la
derecha; <10.2f combina ancho, alineación y decimales; y 010.2f rellena con
ceros hasta 10 caracteres.
Para depurar, el especificador = muestra el nombre y el valor:
Salida esperada:
Y para formatear fechas (el módulo datetime se estudia en
1.14 · Biblioteca estándar):
Salida esperada:
El estilo % (antiguo) existe, pero no lo uses en código nuevo: el f-string es
más legible y potente.
8. Caracteres de escape y cadenas crudas¶
print("Línea1\nLínea2") # salto de línea
print("C:\\ruta\\archivo") # barra invertida
print("Columna1\tColumna2") # tabulador
print(r"ruta\ventas\2026") # cadena cruda: la \ se mantiene literal
Salida esperada:
\n salto de línea, \t tabulador, \\ una barra invertida. Con el prefijo
r"..." (cadena cruda) las \ se toman literalmente: muy útil para
expresiones regulares y rutas de Windows.
9. Otros métodos útiles¶
print("https://x.com/hola".removeprefix("https://")) # quita un prefijo (3.9+)
print("archivo.txt".removesuffix(".txt")) # quita un sufijo
print("a\nb\nc".splitlines()) # divide por saltos
print("nombre=Ana".partition("=")) # parte por el 1º
print("7".zfill(3)) # ceros a la izquierda
print("12".rjust(4, "0")) # alinea a la derecha
print("x".center(7, "-")) # centra
Salida esperada:
removeprefix/removesuffix(desde Python 3.9) limpian prefijos y sufijos.splitlines()divide por saltos de línea (ideal en texto multilínea).partition(sep)devuelve(antes, sep, después); útil para parsear.zfill,rjust,ljust,centeralinean y rellenan.
10. Expresiones regulares (intro)¶
Para buscar patrones más complejos que "una subcadena fija", usamos re:
import re
print(re.search(r"\d+", "pedido 123").group()) # primer número
print(re.findall(r"\d+", "a1 b22 c333")) # todos los números
Salida esperada:
Un patrón se escribe como cadena cruda r"..." para que las \ lleguen
intactas a re. \d = dígito, + = "uno o más". re.search encuentra la
primera coincidencia (y .group() devuelve el texto encontrado); re.findall
devuelve todas en una lista. Si re.search no encuentra nada, devuelve None, y
llamar a .group() sobre None da AttributeError: comprueba el resultado
antes de usarlo. Además puedes reemplazar y dividir:
import re
texto = "El pedido 123 cuesta 45,50 € y el 789 vale 12 €."
print(re.sub(r"\d+", "N", texto)) # sustituye cada número
print(re.split(r",\s*", "a, b,c")) # divide por coma + espacios
Salida esperada:
Y anclar para validar que la cadena coincide completamente:
print(bool(re.fullmatch(r"\d{3}", "123"))) # 3 dígitos exactos
print(bool(re.fullmatch(r"\d{3}", "12"))) # no
Salida esperada:
Con clases de caracteres puedes extraer emails:
texto = "escribe a ana@correo.com y a luis@site.org"
print(re.findall(r"[\w.+-]+@[\w-]+\.[\w.]+", texto))
Salida esperada:
\w = letra/dígito/guion bajo, . entre corchetes es literal, + = uno o más.
Es una introducción; en 1.14 · Biblioteca estándar
se integrará con otras herramientas de la biblioteca estándar sin repetir esta
explicación.
Errores frecuentes¶
- Intentar modificar una cadena (
frase[0] = "x"): son inmutables. - Llamar a un método y no guardar el resultado (
nombre.strip()sinnombre = ...): la cadena original no cambia. - Usar
+en un bucle para construir texto (ineficiente): usajoin. - Comparar sin normalizar (
"Ana "!="ana"): usastrip().lower(). - Creer que
split()sin argumentos separa por comas (separa por espacios). - Confundir
str(texto) coninty sumar texto con número. - Confundir
find(devuelve -1 si no está) conindex(lanzaValueError). - Usar
.format()o%cuando un f-string es más claro. - Usar
re.search(parcial) donde deberíasre.fullmatch(validación completa). - Abusar de regex cuando un método simple (
startswith) basta.
Práctica de transferencia¶
- Normaliza la lista
[" Ana ", "luis", "ANA"]para comparar sin que importen mayúsculas ni espacios. - Dado
"hola mundo hola", cuenta cuántas veces aparece "hola" y lista las palabras únicas. - Extrae los importes de
"Precio: 45,50 € · Iva: 10,50 €"conre. Prueba primero con\d+: ¿qué obtienes y por qué no sirve? Ajusta el patrón para capturar45,50y10,50completos. - Valida un nombre de usuario: solo letras y dígitos, sin espacios (
fullmatch). - Usa
removeprefixypartitionpara extraer el host de"https://miweb.es/pagina". - Con
re.sub, limpia un texto quitando signos de puntuación (.,!?).
Qué debes comprobar: en el punto 1, que los tres nombres quedan en dos valores distintos; en el 4, al menos un caso válido y dos inválidos (con espacio y con símbolo).
Producto evaluable¶
Cuaderno 07_cadenas.py con:
- Un normalizador de texto (
strip().lower()) aplicado a una lista de nombres, con su interpretación. - Un analizador que cuente palabras y muestre las más frecuentes.
- Un extractor de números con
re, un validador de nombre de usuario confullmatch, y un limpiador de puntuación conre.sub.
Formato de entrega: cuaderno marimo ejecutado, con salida esperada, interpretación y conclusión.
Resumen y referencia rápida¶
| Tarea | Método |
|---|---|
| Normalizar | s.strip().lower() |
| Mayúsculas/minúsculas | upper, lower, title |
| Buscar | find, count, startswith, endswith |
| Reemplazar / limpiar | replace, strip |
| Dividir / unir | split(), "sep".join(lista) |
| Validar | isdigit, isalpha, isspace, isalnum |
| Formatear | f"{precio:.2f}", f"{var=}", f"{fecha:%Y-%m-%d}" |
| Prefijo/sufijo | removeprefix, removesuffix |
| Alinear/rellenar | zfill, rjust, ljust, center |
| Parsear | partition(sep), splitlines() |
| Patrones | re.search, re.findall, re.sub, re.fullmatch |
Siguiente: 1.10 · Archivos.