En la lección anterior descubriste que una lista es una secuencia: elementos ordenados a los que se accede por índice. Lo que quizá no viste venir es que llevas usando otra secuencia desde el módulo 2 sin llamarla así. El texto —el tipo str— es exactamente eso: una secuencia de caracteres. Todo lo que aprendiste sobre índices, rebanadas y recorridos con for funciona igual sobre una cadena, con una diferencia decisiva: la cadena es inmutable.

Y esto no es un adorno teórico. En TareaFácil casi todo es texto: los títulos que teclea Marta, los nombres del equipo, las prioridades, las líneas que más adelante guardaremos en un fichero. Manejar bien las cadenas es lo que separa un programa que acepta « CARTEL feria » y «cartel Feria» como dos tareas distintas de uno que entiende que son la misma.

Contenido

  1. La cadena como secuencia inmutable
  2. Índices, rebanadas y recorrido
  3. Limpieza: strip, lstrip, rstrip
  4. Mayúsculas y minúsculas
  5. Búsqueda dentro del texto
  6. Comprobación del contenido
  7. Transformación y relleno
  8. Partición y unión: el puente con las listas
  9. Multilínea, escapes y cadenas crudas
  10. f-strings, str() y Unicode
  11. Comparar y ordenar textos
  12. Aplicación: títulos y códigos de tarea en Estudio Alba
  13. Errores comunes y consejos
  14. Ejercicios
  15. Conclusión

  1. La cadena como secuencia inmutable

Una cadena es una secuencia de caracteres con un orden fijo. len(), los índices y el operador in funcionan igual que en una lista:

titulo = "Cartel feria"
print(len(titulo))          # 12  (los espacios cuentan)
print(titulo[0], titulo[-1])# C l
print("feria" in titulo)    # True

La diferencia está aquí:

titulo[0] = "M"
# TypeError: 'str' object does not support item assignment

Una cadena no se puede modificar. Ni un carácter. Todo lo que parece modificar texto en realidad crea una cadena nueva y deja la original intacta:

titulo = "cartel feria"
titulo.upper()              # crea "CARTEL FERIA" y la tira: no se guarda
print(titulo)               # cartel feria  -> sigue igual
titulo = titulo.upper()     # ahora si: reasignamos el nombre
print(titulo)               # CARTEL FERIA
Lista Cadena
Índices, rebanadas y for Sí Sí
Modificar un elemento Sí No: TypeError
Métodos que modifican en el sitio append, sort… Ninguno
Métodos que devuelven algo nuevo sorted, copy Todos

Retén esta idea, porque explica el error número uno con cadenas: si no guardas el resultado, no ha pasado nada. Y a cambio de la restricción hay una ventaja: al ser inmutables, las cadenas pueden usarse como claves de diccionario, cosa que verás en 05-03.

  1. Índices, rebanadas y recorrido

Todo lo de 05-01 se aplica sin cambios, salvo que aquí el resultado de una rebanada es otra cadena:

cliente = "Panaderia Sole"
print(cliente[0:9])         # Panaderia
print(cliente[-4:])         # Sole
print(cliente[:3].upper())  # PAN   -> tres primeras letras en mayusculas
print(cliente[::-1])        # eloS airedanaP  (del reves)

Para recorrer carácter a carácter, el for directo; y con enumerate si necesitas la posición:

for letra in "Alba":
    print(letra, end=" ")           # A l b a

for i, letra in enumerate("Alba"):
    print(f"{i}:{letra}", end=" ")  # 0:A 1:l 2:b 3:a

El patrón contador de 03-02 funciona igual aquí: recorrer el texto sumando 1 cada vez que la letra cumple una condición. Casi siempre, eso sí, hay un método que lo resuelve en una línea —contar espacios a mano da lo mismo que titulo.count(" ")—, así que recorre carácter a carácter solo cuando la condición sea propia tuya.

  1. Limpieza: strip, lstrip, rstrip

Cuando el texto viene de input() o de un fichero, casi siempre trae basura alrededor: espacios, tabuladores o el salto de línea final. La familia strip la quita.

Método Qué quita Ejemplo
.strip() Espacios de ambos extremos " alba ".strip() → "alba"
.lstrip() Solo por la izquierda " alba".lstrip() → "alba"
.rstrip() Solo por la derecha "alba\n".rstrip() → "alba"
.strip(chars) Cualquiera de esos caracteres "**alba**".strip("*") → "alba"

Con entrada = " Cartel feria del libro \n", f"[{entrada.strip()}]" imprime [Cartel feria del libro]. Ojo con un malentendido frecuente: .strip("abc") no borra la subcadena "abc", sino cualquier a, b o c que haya en los extremos. Y strip nunca toca el interior: " a b ".strip() deja "a b".

  1. Mayúsculas y minúsculas

Método Efecto "cartel FERIA del libro" da
.upper() Todo mayúsculas CARTEL FERIA DEL LIBRO
.lower() Todo minúsculas cartel feria del libro
.title() Primera letra de cada palabra Cartel Feria Del Libro
.capitalize() Primera letra de la cadena, resto en minúsculas Cartel feria del libro
.casefold() Como lower, pero más agresivo cartel feria del libro

Los dos usos típicos son distintos y conviene no mezclarlos. Para comparar, se normaliza a minúsculas (nadie debería teclear «Marta» con la mayúscula exacta para que el programa lo acepte). Para mostrar, se elige la forma bonita:

respuesta = "  MARTA "
print(respuesta.strip().lower() in ("marta", "luis", "nuria"))  # True
print(respuesta.strip().capitalize())                           # Marta

.casefold() es la versión rigurosa de .lower() para comparaciones internacionales: en alemán convierte la ß en ss, cosa que .lower() no hace. Para español, ambas se comportan igual; usa .casefold() si comparas texto que puede venir en cualquier idioma.

  1. Búsqueda dentro del texto

Método Devuelve Si no encuentra
subcadena in texto True / False False
.find(sub) Índice de la primera aparición -1
.rfind(sub) Índice de la última aparición -1
.index(sub) Índice de la primera aparición ValueError
.count(sub) Número de apariciones 0
.startswith(pre) True si empieza así False
.endswith(suf) True si termina así False
titulo = "Cartel feria del libro, feria de otono"
print("feria" in titulo)            # True   -> lo mas legible si solo quieres saber si esta
print(titulo.find("feria"))         # 7
print(titulo.rfind("feria"))        # 23
print(titulo.find("cartel"))        # -1     -> distingue mayusculas
print(titulo.count("feria"))        # 2
print(titulo.startswith("Cartel"))  # True
print(titulo.endswith((".png", ".pdf")))    # False -> admite una tupla de opciones

La diferencia entre .find() y .index() es la misma que ya conoces de las listas: find devuelve -1 cuando no hay nada, mientras que index lanza ValueError. Usa find cuando «no estar» sea un caso normal, y recuerda que -1 es un índice válido en Python, así que nunca uses el resultado de find sin comprobar antes que no es -1.

  1. Comprobación del contenido

Estos métodos devuelven True o False y sirven para validar antes de convertir, tal como hacías en 02-04.

Método True cuando la cadena…
.isdigit() Solo contiene dígitos (y no está vacía)
.isalpha() Solo contiene letras
.isalnum() Solo contiene letras o dígitos
.isspace() Solo contiene espacios en blanco
print("365".isdigit(), "3.5".isdigit(), "-3".isdigit())     # True False False
print("Nuria".isalpha(), "Nuria 1".isalpha())               # True False
print("ALB01".isalnum(), "".isalnum())                      # True False
print("   ".isspace())                                      # True

Fíjate en las trampas: "3.5" no es isdigit (el punto no es un dígito), "-3" tampoco (el signo no lo es), y una cadena vacía devuelve False en todos ellos. Por eso if texto.isdigit(): antes de int(texto) sigue siendo la guarda correcta para enteros positivos, pero no vale para decimales ni negativos. La forma robusta de gestionar esos casos es try/except, y llega en Depuración y manejo de errores.

  1. Transformación y relleno

Método Qué hace Ejemplo
.replace(viejo, nuevo) Sustituye todas las apariciones "a-b-c".replace("-", " ") → "a b c"
.replace(v, n, cuenta) Solo las primeras cuenta "a-b-c".replace("-", " ", 1) → "a b-c"
.zfill(n) Rellena con ceros por la izquierda "7".zfill(3) → "007"
.center(n, c) Centra en n caracteres "ALB".center(9, "-") → "---ALB---"
.ljust(n, c) Alinea a la izquierda "ALB".ljust(6, ".") → "ALB..."
.rjust(n, c) Alinea a la derecha "ALB".rjust(6, ".") → "...ALB"

zfill es el atajo clásico para numerar: str(7).zfill(2) da "07", y así los códigos salen 01, 02, 03… en vez de 1, 2, 3. Los tres de alineación hacen lo mismo que los especificadores :<, :^ y :> de las f-strings; puedes usar cualquiera de las dos formas, aunque dentro de una f-string queda más limpio el especificador.

  1. Partición y unión: el puente con las listas

Aquí está la conexión directa con la lección anterior, y es de las cosas más útiles del lenguaje.

split(separador) parte una cadena y devuelve una lista de trozos. Sin argumentos, parte por espacios (agrupando los consecutivos).

linea = "Cartel feria;luis;alta;3"
print(linea.split(";"))     # ['Cartel feria', 'luis', 'alta', '3']  -> 4 campos
print("  Cartel   feria ".split())      # ['Cartel', 'feria']
print(linea.split(";", 1))  # ['Cartel feria', 'luis;alta;3']  -> solo el primer corte
Método Qué hace
.split(sep) Parte por sep de izquierda a derecha; sin sep, por espacios
.rsplit(sep, n) Igual, pero cuenta los n cortes desde la derecha
.splitlines() Parte por saltos de línea, sin dejar el \n

join hace lo contrario: une los elementos de una lista en una sola cadena. Se llama sobre el separador, que es lo que despista al principio:

equipo = ["Marta", "Luis", "Nuria"]
print(", ".join(equipo))    # Marta, Luis, Nuria
print("".join(equipo))      # MartaLuisNuria
print(", ".join([str(d) for d in [3, 5, 2]]))   # 3, 5, 2

Se lee así: «coge la lista y pégala usando esta cadena como pegamento». La tercera línea recuerda que join solo funciona con listas de cadenas: si hay números, hay que convertirlos antes, normalmente con una comprensión de 05-01.

split y join son la pareja que convierte texto en datos y datos en texto. Cuando en 05-05 leamos un fichero de tareas, cada línea se convertirá en campos con split; y para escribirla, los campos volverán a ser una línea con join.

  1. Multilínea, escapes y cadenas crudas

Para un texto de varias líneas están las comillas triples, que respetan los saltos tal cual se escriben:

AYUDA = """TareaFacil - ayuda rapida
  1  Registrar una tarea
  2  Ver el listado completo
  5  Salir"""
print(AYUDA)

Dentro de una cadena normal, algunos caracteres se escriben con una secuencia de escape, que empieza por barra invertida:

Escape Significa
\n Salto de línea
\t Tabulador
\\ Una barra invertida literal
\" y \' Comilla doble o simple dentro de la cadena
print("Marta\tLuis\tNuria")             # separados por tabuladores
print("Ruta: C:\\alba\\tareas")         # Ruta: C:\alba\tareas
print('Dijo "vale" y se fue')           # comillas simples fuera, dobles dentro
print(r"C:\alba\tareas")                # C:\alba\tareas  -> cadena cruda

La última línea usa el prefijo r de cadena cruda (raw), que desactiva los escapes y resulta comodísimo con rutas de Windows. Cuidado con la trampa clásica: "C:\tareas" no es lo que parece, porque \t es un tabulador; con r"C:\tareas" no hay sorpresa.

  1. f-strings, str() y Unicode

Las f-strings de 02-03 son la forma normal de construir texto con valores dentro, y sus especificadores de anchura son los que dan a TareaFácil sus tablas alineadas:

titulo, responsable, dias = "Cartel feria", "Marta", 3
print(f"{titulo:<24}{responsable:>10}{dias:>5}")    # ancho fijo por columna
print(f"Media: {10 / 3:.2f}")                       # dos decimales: 3.33

str(valor) convierte cualquier valor en su representación textual, y es imprescindible antes de concatenar con + o de usar join. Recuerda que "Dias: " + 3 da TypeError, mientras que "Dias: " + str(3) funciona y f"Dias: {3}" funciona sin conversión explícita.

Una idea sobre Unicode, que basta por ahora. Cada carácter de una cadena de Python es un carácter, no un byte: len("Panadería") vale 9 aunque la í lleve tilde, y "ñ"[0] es "ñ". Pero cuando ese texto sale del programa —a un fichero, a la red— hay que codificarlo en bytes, y ahí es donde entra UTF-8, la codificación estándar que sabe representar tildes, ñ, ç y prácticamente cualquier alfabeto. Si abres un fichero con la codificación equivocada verás cosas como Panadería. Por eso en Guardar datos en archivos escribiremos siempre encoding="utf-8".

  1. Comparar y ordenar textos

Las cadenas se comparan con <, > y == carácter a carácter, según el número que cada carácter tiene asignado en la tabla Unicode. Y ahí aparece una sorpresa:

print("Zapato" < "abeto")       # True
print("alta" < "baja")          # True
print("Marta" == "marta")       # False

"Zapato" < "abeto" es True porque todas las mayúsculas van antes que todas las minúsculas: la Z es el carácter 90 y la a el 97. No es un capricho: es el orden de la tabla, no el del diccionario de la RAE. La consecuencia práctica es que sorted(["nuria", "Luis", "Marta"]) pone primero todo lo que empiece por mayúscula. La solución, que ya viste en 04-05 y 05-01, es ordenar por una clave normalizada:

print(sorted(["ana", "Zoe"]))                   # ['Zoe', 'ana']  -> mayusculas primero
print(sorted(["ana", "Zoe"], key=str.lower))    # ['ana', 'Zoe']  -> orden esperado

Lo mismo vale para comparar: normaliza los dos lados antes de comparar. entrada.strip().lower() == "marta" acepta " Marta ", "MARTA" y "marta". Las tildes añaden otro escalón ("Alvaro" < "Álvaro"), y su tratamiento correcto requiere bibliotecas de intercalación que quedan fuera de este curso.

  1. Aplicación: títulos y códigos de tarea en Estudio Alba

Tres utilidades de texto para TareaFácil, escritas con lo de esta lección. La estructura del programa no cambia todavía —la tarea sigue siendo un puñado de variables hasta 05-03—, pero estas funciones entrarán en la sección de lógica.

def normalizar_titulo(texto):
    """Devuelve el titulo sin espacios sobrantes ni dobles, con la inicial en mayuscula."""
    limpio = " ".join(texto.split())        # quita extremos y espacios repetidos
    return limpio.capitalize()

def codigo_tarea(cliente, titulo, numero):
    """Devuelve un codigo tipo ALB-PAN-01 a partir del cliente y del titulo."""
    siglas_cliente = cliente.strip().replace(" ", "")[:3].upper()
    inicial = titulo.strip()[:1].upper()
    return f"ALB-{siglas_cliente}-{str(numero).zfill(2)}{inicial}"

def partir_linea(linea):
    """Parte 'titulo;responsable;prioridad;dias' y devuelve los cuatro campos."""
    campos = linea.strip().split(";")
    if len(campos) != 4:
        print("Linea mal formada:", linea)
        return None
    titulo, responsable, prioridad, dias = campos
    return (normalizar_titulo(titulo), responsable.strip().capitalize(),
            prioridad.strip().lower(), int(dias))

Probémoslas:

print(normalizar_titulo("   cartel   FERIA del libro "))        # Cartel feria del libro
print(codigo_tarea("Panaderia Sole", "Menu de temporada", 1))   # ALB-PAN-01M
print(partir_linea("Cartel feria;luis;alta;3"))     # ('Cartel feria', 'Luis', 'alta', 3)

Comentario de lo importante:

  • " ".join(texto.split()) es el idioma clásico para colapsar espacios: split() sin argumentos parte por cualquier bloque de blancos y descarta los vacíos; join los vuelve a unir con exactamente un espacio. Resuelve de golpe los espacios de los extremos, los dobles y los tabuladores.
  • cliente.replace(" ", "")[:3].upper() construye las siglas: quita los espacios, coge tres caracteres con una rebanada y los pasa a mayúsculas. Si el cliente tuviera menos de tres letras, la rebanada devuelve lo que haya sin error.
  • str(numero).zfill(2) convierte primero a texto (zfill es un método de cadena, no de entero) y luego rellena con ceros.
  • partir_linea devuelve None si el número de campos no es el esperado, aplicando la cláusula de guarda de 04-02, y normaliza cada campo con las convenciones del programa: título capitalizado, responsable capitalizado, prioridad en minúsculas y días convertidos a entero.

Esa función partir_linea es, de hecho, media lección 05-05: leer un fichero de tareas consistirá en aplicarla a cada línea.

Errores Comunes y Consejos

Olvidar que los métodos no modifican. titulo.upper() sin asignar no hace nada visible; si el texto no cambia, comprueba que has escrito titulo = titulo.upper(). Y titulo[0] = "M" da TypeError, porque la cadena es inmutable: para cambiar un carácter hay que reconstruir, "M" + titulo[1:].

Usar el resultado de .find() sin comprobarlo. Si no encuentra nada devuelve -1, y texto[-1] es el último carácter, así que el programa seguirá adelante con un dato erróneo en vez de fallar.

Comparar sin normalizar. entrada == "marta" falla con " Marta ". Aplica .strip().lower() a los dos lados, siempre en el mismo sitio del programa.

Creer que .strip("hola") borra la palabra «hola». Borra cualquier h, o, l o a de los extremos. Para quitar una palabra concreta, .replace("hola", "").

join con números. ", ".join([3, 5]) lanza TypeError. Convierte antes: ", ".join([str(d) for d in dias]).

Consejo: normaliza en la entrada, formatea en la salida. Guarda los datos ya limpios (una sola forma canónica) y decide la presentación al imprimir. Así nunca tendrás dos versiones de «Cartel feria» conviviendo en la misma agenda.

Ejercicios

Ejercicio 1: Predecir la salida

Di qué imprime cada línea y explica por qué.

texto = "  Menu Panaderia SOLE  "
print(f"[{texto.strip().lower()}]")
print(texto.find("Sole"), texto.find("SOLE"))
print(texto.strip().split())
print("-".join(texto.split()))
print(texto.replace(" ", ""))
print("SOLE" < "Sole", "SOLE".isalpha())

Ejercicio 2: Ficha de texto

Escribe resumen_titulo(titulo) que reciba un título y devuelva una cadena de una línea con: el título normalizado (sin espacios sobrantes y capitalizado), su número de caracteres, su número de palabras y sus iniciales en mayúsculas separadas por puntos. Para " cartel feria del libro " debe devolver Cartel feria del libro | 22 car. | 4 pal. | C.F.D.L.

Ejercicio 3: Lector de líneas del estudio

Dada esta lista de líneas, escribe un programa que las parta, descarte las mal formadas (que no tengan cuatro campos o cuyos días no sean un entero) e imprima una tabla alineada con título, responsable capitalizado, prioridad en mayúsculas y días. Al final, imprime el total de días de las líneas válidas.

lineas = [
    "Cartel feria;luis;alta;3",
    "  menu panaderia sole ; MARTA ; media ; 5 ",
    "Logotipo Vidal;nuria;baja",
    "Flyer verano;marta;alta;dos",
]

Soluciones

Solución 1.

[menu panaderia sole]
-1 15
['Menu', 'Panaderia', 'SOLE']
Menu-Panaderia-SOLE
MenuPanaderiaSOLE
True True

strip().lower() limpia y baja todo. find("Sole") da -1 porque la búsqueda distingue mayúsculas y en el texto pone SOLE; find("SOLE") sí lo encuentra, en el índice 15 contando los dos espacios iniciales. split() sin argumentos ignora los espacios sobrantes. join pega los trozos con guiones. replace(" ", "") elimina todos los espacios, también los interiores. Y "SOLE" < "Sole" es True porque la O mayúscula (79) va antes que la o minúscula (111).

Solución 2.

def resumen_titulo(titulo):
    """Devuelve una linea con el titulo normalizado y sus estadisticas."""
    limpio = " ".join(titulo.split()).capitalize()
    palabras = limpio.split()
    iniciales = ".".join([p[0].upper() for p in palabras]) + "."
    return f"{limpio} | {len(limpio)} car. | {len(palabras)} pal. | {iniciales}"

print(resumen_titulo("  cartel  feria del libro "))
# Cartel feria del libro | 22 car. | 4 pal. | C.F.D.L.

La clave está en calcular limpio una sola vez y medir sobre él: si contaras los caracteres del original obtendrías 26 por culpa de los espacios sobrantes. Las iniciales combinan una comprensión de 05-01 (p[0].upper() para cada palabra) con join, y el punto final se añade aparte porque join solo pone separadores entre elementos.

Solución 3.

total = 0
print(f"{'TITULO':<24}{'RESP.':>10}{'PRIOR.':>10}{'DIAS':>6}")
for linea in lineas:
    campos = linea.strip().split(";")
    if len(campos) != 4 or not campos[3].strip().isdigit():
        print(f"Descartada: {linea.strip()}")
        continue
    titulo = " ".join(campos[0].split()).capitalize()
    responsable = campos[1].strip().capitalize()
    prioridad = campos[2].strip().upper()
    dias = int(campos[3].strip())
    total += dias
    print(f"{titulo:<24}{responsable:>10}{prioridad:>10}{dias:>6}")
print(f"{'TOTAL':<24}{total:>26}")

Salen dos filas válidas —«Cartel feria / Luis / ALTA / 3» y «Menu panaderia sole / Marta / MEDIA / 5», total 8 días— y dos descartes: la tercera línea solo tiene tres campos y la cuarta trae "dos" donde debería ir un número. Dos detalles que valen para cualquier lector de datos. La comprobación len(campos) != 4 or not campos[3].strip().isdigit() reúne las dos causas de descarte en una sola guarda, y el continue de 03-03 evita anidar el resto del cuerpo dentro de un if. Y cada campo se limpia con .strip() antes de usarlo, porque la segunda línea trae espacios alrededor de los puntos y comas: sin ese strip, " 5 ".isdigit() daría False y la línea buena se descartaría.

Conclusión

Una cadena es una secuencia inmutable de caracteres: acepta índices, rebanadas y recorridos con for igual que una lista, pero no se puede modificar, así que todos sus métodos devuelven una cadena nueva y hay que guardarla. Has recorrido las seis familias de métodos: limpieza (strip, lstrip, rstrip), mayúsculas y minúsculas (upper, lower, title, capitalize, casefold), búsqueda (in, find, rfind, index, count, startswith, endswith), comprobación (isdigit, isalpha, isalnum, isspace), transformación (replace, zfill, center, ljust, rjust) y partición y unión (split, rsplit, splitlines, join), que es el puente entre el texto y las listas. Sabes escribir textos multilínea con """, protegerte de los escapes con r"", alinear columnas con f-strings, y por qué "Zapato" < "abeto" obliga a comparar y ordenar sobre una versión en minúsculas. Y sabes que las tildes y la ñ son un carácter cada una, pero que al salir a disco el texto se codifica en bytes y por eso UTF-8 importa.

En Estudio Alba ya puedes normalizar los títulos para que « CARTEL feria » y «Cartel Feria» sean el mismo texto, generar códigos como ALB-PAN-01M y convertir una línea "Cartel feria;luis;alta;3" en cuatro campos listos para usar. Lo que sigue sin resolverse es el problema que 05-01 dejó abierto: esos cuatro campos siguen siendo cuatro variables sueltas, o peor, cuatro listas paralelas que hay que mantener sincronizadas a mano. La solución llega en Diccionarios y conjuntos: una estructura donde cada dato se guarda con su nombre —titulo, responsable, prioridad, dias, completada— en lugar de con un número de posición. Ahí TareaFácil dará el salto a la v0.9 y una tarea pasará a ser, por fin, una sola cosa.

Fundamentos de la Programación

Módulo 1: Introducción a la Programación

Módulo 2: Conceptos Básicos

Módulo 3: Estructuras de Control

Módulo 4: Funciones y Procedimientos

Módulo 5: Estructuras de Datos

Módulo 6: Algoritmos Básicos

Módulo 7: Objetos y Organización del Código

Módulo 8: Buenas Prácticas y Herramientas

Módulo 9: Proyecto Final y Cierre del Curso

© Copyright 2026. Todos los derechos reservados