CAP 14 · LEC 04·Bonus prácticos

pathlib: rutas y archivos con la API moderna de Python

pathlib.Path reemplaza os.path con una API orientada a objetos. path / 'subdir' / 'file.txt' es más legible que os.path.join. Leer, escribir, listar y glob incluidos.

● INTERMEDIO8 min lectura4 ejerciciospor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

Path básico y el operador /

pathlib.Path representa una ruta del sistema de archivos. El operador / une componentes de ruta de forma idiomática, independientemente del sistema operativo.

from pathlib import Path # Crear objetos Path ruta = Path("/tmp/proyecto") relativa = Path("src/utils") # Operador / para unir rutas (cross-platform) archivo = ruta / "data" / "config.json" print(archivo) # /tmp/proyecto/data/config.json # Alternativa con os.path (más verbosa) import os.path viejo = os.path.join("/tmp/proyecto", "data", "config.json") print(viejo == str(archivo)) # True — mismo resultado # Propiedades del Path p = Path("/home/user/docs/report.pdf") print(p.name) # report.pdf print(p.stem) # report (sin extensión) print(p.suffix) # .pdf print(p.parent) # /home/user/docs print(p.parts) # ('/', 'home', 'user', 'docs', 'report.pdf') # Path relativo a partir de otro base = Path("/home/user") docs = base / "docs" print(docs.relative_to(base)) # docs # Path.home() y Path.cwd() print(Path.home()) # /Users/usuario (el directorio home del usuario) print(Path.cwd()) # directorio de trabajo actual # Convertir a string cuando necesitas interoperar con APIs viejas print(str(archivo)) # /tmp/proyecto/data/config.json print(archivo.as_posix()) # siempre con /, incluso en Windows
Salida/tmp/proyecto/data/config.json True report.pdf report .pdf /home/user/docs ('/', 'home', 'user', 'docs', 'report.pdf') docs

read_text() y write_text()

Path.read_text() y Path.write_text() son el acceso más directo para leer y escribir archivos de texto sin gestionar el open() explícitamente.

from pathlib import Path import tempfile # Crear un archivo temporal para los ejemplos tmp_dir = Path(tempfile.mkdtemp()) archivo = tmp_dir / "notas.txt" # write_text: escribe y crea el archivo si no existe archivo.write_text("Primera línea Segunda línea Tercera línea ", encoding="utf-8") print(f"Escrito: {archivo.stat().st_size} bytes") # read_text: lee todo el contenido como string contenido = archivo.read_text(encoding="utf-8") print(contenido) # Equivalente a open() pero más conciso # with open(archivo, "r", encoding="utf-8") as f: # contenido = f.read() # Leer y escribir bytes con read_bytes() / write_bytes() imagen_bytes = bytes([0x89, 0x50, 0x4E, 0x47]) # header PNG simulado (tmp_dir / "imagen.png").write_bytes(imagen_bytes) leidos = (tmp_dir / "imagen.png").read_bytes() print(leidos.hex()) # 89504e47 # Añadir contenido al final (no hay append_text, se hace con open) with archivo.open("a", encoding="utf-8") as f: f.write("Cuarta línea ") # Leer líneas directamente lineas = archivo.read_text(encoding="utf-8").splitlines() print(f"Total líneas: {len(lineas)}") # 4 # Limpiar import shutil shutil.rmtree(tmp_dir)
SalidaEscrito: 44 bytes Primera línea Segunda línea Tercera línea 89504e47 Total líneas: 4

glob y rglob para buscar archivos

glob(patrón) busca archivos que coincidan con el patrón en el directorio actual. rglob(patrón) hace lo mismo recursivamente en todos los subdirectorios.

from pathlib import Path import tempfile, os # Crear estructura de archivos de prueba base = Path(tempfile.mkdtemp()) (base / "src").mkdir() (base / "tests").mkdir() (base / "src" / "main.py").write_text("# main") (base / "src" / "utils.py").write_text("# utils") (base / "tests" / "test_main.py").write_text("# tests") (base / "README.md").write_text("# readme") (base / "config.json").write_text("{}") # glob: busca en el directorio actual (no recursivo) py_files = list(base.glob("*.py")) # solo en base/ json_files = list(base.glob("*.json")) print(f"Python en raíz: {[f.name for f in py_files]}") # [] print(f"JSON en raíz: {[f.name for f in json_files]}") # ['config.json'] # rglob: busca en todos los subdirectorios all_py = list(base.rglob("*.py")) all_py.sort() print(f"Todo .py: {[f.name for f in all_py]}") # ['main.py', 'test_main.py', 'utils.py'] # glob con patrón de directorio test_files = list(base.glob("tests/*.py")) print(f"Tests: {[f.name for f in test_files]}") # ['test_main.py'] # Caso real: procesar todos los archivos de un tipo total_lines = 0 for py_file in base.rglob("*.py"): lines = len(py_file.read_text().splitlines()) total_lines += lines print(f" {py_file.relative_to(base)}: {lines} línea(s)") print(f"Total: {total_lines} líneas") import shutil shutil.rmtree(base)
SalidaPython en raíz: [] JSON en raíz: ['config.json'] Todo .py: ['main.py', 'test_main.py', 'utils.py'] Tests: ['test_main.py'] src/main.py: 1 línea(s) src/utils.py: 1 línea(s) tests/test_main.py: 1 línea(s) Total: 3 líneas

exists(), mkdir(), is_file(), is_dir()

Los métodos de inspección y creación de rutas hacen que el trabajo con el sistema de archivos sea seguro y expresivo.

from pathlib import Path import tempfile, shutil base = Path(tempfile.mkdtemp()) # Verificar existencia print(base.exists()) # True print(base.is_dir()) # True print(base.is_file()) # False archivo = base / "data.txt" print(archivo.exists()) # False — aún no existe # Crear directorios # mkdir() con parents=True crea toda la jerarquía deep = base / "a" / "b" / "c" deep.mkdir(parents=True, exist_ok=True) print(deep.exists()) # True # exist_ok=True evita el error si ya existe deep.mkdir(parents=True, exist_ok=True) # sin error # Crear archivo y verificar archivo.write_text("contenido") print(archivo.exists()) # True print(archivo.is_file()) # True print(archivo.is_dir()) # False # stat() para metadatos del archivo stat = archivo.stat() print(f"Tamaño: {stat.st_size} bytes") print(f"Permisos: {oct(stat.st_mode)}") # rename() para mover/renombrar nuevo_nombre = base / "data_v2.txt" archivo.rename(nuevo_nombre) print(nuevo_nombre.exists()) # True print(archivo.exists()) # False — fue renombrado # unlink() para eliminar archivo nuevo_nombre.unlink() print(nuevo_nombre.exists()) # False # rmdir() solo elimina directorios vacíos # Para eliminar con contenido usar shutil.rmtree() shutil.rmtree(base)
SalidaTrue True False False True True True False Tamaño: 9 bytes True False False

Practica