Curso de Python · Bloque 05 de 09

Entrada y salida

Consola, archivos, rutas, CSV, JSON y flujos.

Audiencia. Personas que ya han visto algo de programación y quieren repasar Python de forma práctica.
Prerrequisitos. Funciones, cadenas, diccionarios y manejo básico de iterables.
Duración orientativa. 60–90 minutos, incluida la práctica.

Objetivos de aprendizaje

  • Mostrar y formatear resultados con print.
  • Separar la lectura interactiva de la lógica de negocio.
  • Leer y escribir texto con pathlib y gestores de contexto.
  • Intercambiar datos tabulares y estructurados con CSV y JSON.

Itinerario

  1. Salida estándar
  2. Entrada
  3. Rutas y texto
  4. CSV
  5. JSON
  6. Flujos
  7. Práctica

Cómo trabajar: ejecuta las celdas en orden, predice el resultado antes de verlo y modifica los ejemplos. Cada bloque termina con un ejercicio, un punto de partida y una solución posible.

1. Salida estándar

print convierte los argumentos a texto. sep controla el separador y end el final. Para resultados reutilizables, prefiere devolver datos desde una función y deja la presentación en otra capa.

Python
nombre, puntos = "Mina", 93.456
print("Usuario", nombre, "puntos", f"{puntos:.1f}", sep=" | ")
print("Procesando", end="… ")
print("listo")
Resultado
Usuario | Mina | puntos | 93.5
Procesando… listo

Las f-strings permiten ancho, alineación, relleno, porcentaje, separadores de miles y fechas. Mantienen cerca el valor y su formato.

Python
ventas = [("Norte", 12540.5), ("Sur", 987.25), ("Este", 6340.0)]
for zona, importe in ventas:
    print(f"{zona:<8} {importe:>12,.2f} €")
Resultado
Norte       12,540.50 €
Sur            987.25 €
Este         6,340.00 €

2. Entrada interactiva

input(mensaje) siempre devuelve una cadena. En un notebook no queremos bloquear la ejecución esperando teclado, así que aislamos la conversión en una función y le inyectamos una fuente de entrada simulada para probarla.

Python
def pedir_entero(mensaje, leer=input):
    texto = leer(mensaje)
    return int(texto)

def entrada_simulada(mensaje):
    print(mensaje + "42")
    return "42"

respuesta = pedir_entero("Escribe un entero: ", leer=entrada_simulada)
print("Doble:", respuesta * 2)
Resultado
Escribe un entero: 42
Doble: 84

3. Rutas y archivos de texto

pathlib.Path compone rutas de forma portable. Especifica encoding="utf-8". Para que el curso no deje basura, los ejemplos trabajan en un directorio temporal que se elimina al final.

Python
from pathlib import Path
import tempfile

_directorio_temporal = tempfile.TemporaryDirectory()
carpeta = Path(_directorio_temporal.name)
ruta_texto = carpeta / "notas.txt"

ruta_texto.write_text("Primera línea\nSegunda línea: ñ y €\n", encoding="utf-8")
contenido = ruta_texto.read_text(encoding="utf-8")
print(contenido)
Resultado
Primera línea
Segunda línea: ñ y €

Para archivos grandes, recorre línea a línea. El gestor with cierra el recurso incluso si ocurre un error. Los modos habituales son r (leer), w (reemplazar), a (añadir) y b (binario).

Python
with ruta_texto.open("a", encoding="utf-8") as archivo:
    archivo.write("Tercera línea\n")

with ruta_texto.open("r", encoding="utf-8") as archivo:
    lineas_limpias = [linea.strip() for linea in archivo]

print(lineas_limpias)
Resultado
['Primera línea', 'Segunda línea: ñ y €', 'Tercera línea']

4. CSV: datos tabulares

El módulo csv conoce comillas, delimitadores y saltos de línea. DictWriter y DictReader conectan naturalmente con diccionarios. Abre con newline="" para que el módulo gestione los finales de línea.

Python
import csv

ruta_csv = carpeta / "personas.csv"
personas = [
    {"nombre": "Ana", "edad": 31, "ciudad": "Lugo"},
    {"nombre": "Leo", "edad": 27, "ciudad": "León"},
]

with ruta_csv.open("w", encoding="utf-8", newline="") as archivo:
    escritor = csv.DictWriter(archivo, fieldnames=["nombre", "edad", "ciudad"])
    escritor.writeheader()
    escritor.writerows(personas)

with ruta_csv.open("r", encoding="utf-8", newline="") as archivo:
    leidas = list(csv.DictReader(archivo))

print(leidas)  # CSV devuelve texto; convierte tipos cuando sea necesario.
Resultado
[{'nombre': 'Ana', 'edad': '31', 'ciudad': 'Lugo'}, {'nombre': 'Leo', 'edad': '27', 'ciudad': 'León'}]

5. JSON: datos estructurados

JSON admite objetos, listas, cadenas, números, booleanos y null. En Python corresponden aproximadamente a dict, list, str, int/float, bool y None. No preserva tuplas ni clases personalizadas por sí solo.

Python
import json

configuracion = {
    "tema": "oscuro",
    "notificaciones": True,
    "idiomas": ["es", "en"],
    "limite": None,
}
ruta_json = carpeta / "config.json"
ruta_json.write_text(
    json.dumps(configuracion, ensure_ascii=False, indent=2),
    encoding="utf-8",
)
recuperada = json.loads(ruta_json.read_text(encoding="utf-8"))
print(ruta_json.read_text(encoding="utf-8"))
assert recuperada == configuracion
Resultado
{
  "tema": "oscuro",
  "notificaciones": true,
  "idiomas": [
    "es",
    "en"
  ],
  "limite": null
}

6. Flujos en memoria

Muchas APIs aceptan un objeto parecido a un archivo. io.StringIO permite probar escritura y lectura sin tocar disco. Para bytes existe io.BytesIO.

Python
from io import StringIO

flujo = StringIO()
print("línea uno", file=flujo)
print("línea dos", file=flujo)
texto_generado = flujo.getvalue()
print(repr(texto_generado))
Resultado
'línea uno\nlínea dos\n'

Errores habituales

  • Confiar en que input devuelve un número sin convertir ni validar.
  • Abrir texto sin declarar codificación.
  • Construir rutas concatenando / o \; usa Path.
  • Usar eval para leer datos: ejecuta código arbitrario. Prefiere JSON.
  • Suponer que CSV conserva tipos.

Extensión opcional: prueba argparse para programas de línea de comandos y tomllib para configuración TOML.

Práctica · Informe de contactos

Escribe crear_informe(contactos, ruta) que guarde un JSON UTF-8 con:

  • el total de contactos;
  • las ciudades únicas ordenadas;
  • los propios contactos.

Después, vuelve a leerlo y comprueba el resultado.

Tu turno
def crear_informe(contactos, ruta):
    # TODO: construye el informe, escríbelo y devuélvelo.
    pass
Ver una solución posible
Solución
import json

def crear_informe(contactos, ruta):
    informe = {
        "total": len(contactos),
        "ciudades": sorted({c["ciudad"] for c in contactos}),
        "contactos": contactos,
    }
    ruta.write_text(
        json.dumps(informe, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
    return informe

contactos = [
    {"nombre": "Ana", "ciudad": "Lugo"},
    {"nombre": "Eva", "ciudad": "Lugo"},
    {"nombre": "Leo", "ciudad": "León"},
]
ruta_informe = carpeta / "informe.json"
esperado = crear_informe(contactos, ruta_informe)
leido = json.loads(ruta_informe.read_text(encoding="utf-8"))
print(leido)
assert leido == esperado and leido["total"] == 3
Resultado
{'total': 3, 'ciudades': ['León', 'Lugo'], 'contactos': [{'nombre': 'Ana', 'ciudad': 'Lugo'}, {'nombre': 'Eva', 'ciudad': 'Lugo'}, {'nombre': 'Leo', 'ciudad': 'León'}]}
Python
# Limpieza del directorio temporal creado para los ejemplos.
_directorio_temporal.cleanup()
print("Recursos temporales eliminados")
Resultado
Recursos temporales eliminados

Comprobación rápida

  • [ ] Sé separar entrada, lógica y presentación.
  • [ ] Puedo leer y escribir texto UTF-8 con Path.
  • [ ] Distingo cuándo usar CSV y cuándo JSON.

← Anterior · Volver al índice · Siguiente →