Saltar a contenido

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:

P
o
Python
oditrevid se nohtyP
19

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:

frase[0] = "J"

Salida esperada (error):

TypeError: 'str' object does not support item assignment

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:

HOLA MUNDO
hola
Ana Lopez

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:

7
2
True
True

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:

hola
a-b-c
Julia es divertido

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:

['Python', 'es', 'divertido']
a-b-c
['a', 'b', 'c']

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:

True
True
True
True

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:

Ana tiene 20 años
Hola Ana

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:

1234.57
1,234.57
  1234.567
|1234.57   |
0001234.57

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:

nombre = "Ana"
print(f"{nombre=}")         # nombre='Ana'

Salida esperada:

nombre='Ana'

Y para formatear fechas (el módulo datetime se estudia en 1.14 · Biblioteca estándar):

from datetime import datetime
fecha = datetime(2026, 8, 21)
print(f"{fecha:%Y-%m-%d}")

Salida esperada:

2026-08-21

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:

Línea1
Línea2
C:\ruta\archivo
Columna1    Columna2
ruta\ventas\2026

\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:

x.com/hola
archivo
['a', 'b', 'c']
('nombre', '=', 'Ana')
007
0012
---x---
  • 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, center alinean 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:

123
['1', '22', '333']

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:

El pedido N cuesta N,N € y el N vale N €.
['a', 'b', 'c']

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:

True
False

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:

['ana@correo.com', 'luis@site.org']

\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() sin nombre = ...): la cadena original no cambia.
  • Usar + en un bucle para construir texto (ineficiente): usa join.
  • Comparar sin normalizar ("Ana " != "ana"): usa strip().lower().
  • Creer que split() sin argumentos separa por comas (separa por espacios).
  • Confundir str (texto) con int y sumar texto con número.
  • Confundir find (devuelve -1 si no está) con index (lanza ValueError).
  • Usar .format() o % cuando un f-string es más claro.
  • Usar re.search (parcial) donde deberías re.fullmatch (validación completa).
  • Abusar de regex cuando un método simple (startswith) basta.

Práctica de transferencia

  1. Normaliza la lista [" Ana ", "luis", "ANA"] para comparar sin que importen mayúsculas ni espacios.
  2. Dado "hola mundo hola", cuenta cuántas veces aparece "hola" y lista las palabras únicas.
  3. Extrae los importes de "Precio: 45,50 € · Iva: 10,50 €" con re. Prueba primero con \d+: ¿qué obtienes y por qué no sirve? Ajusta el patrón para capturar 45,50 y 10,50 completos.
  4. Valida un nombre de usuario: solo letras y dígitos, sin espacios (fullmatch).
  5. Usa removeprefix y partition para extraer el host de "https://miweb.es/pagina".
  6. 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:

  1. Un normalizador de texto (strip().lower()) aplicado a una lista de nombres, con su interpretación.
  2. Un analizador que cuente palabras y muestre las más frecuentes.
  3. Un extractor de números con re, un validador de nombre de usuario con fullmatch, y un limpiador de puntuación con re.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.