Python Pathlib: La Guía Moderna para Manejar Rutas de Archivos
Updated on
Si alguna vez has escrito código Python como os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'output'), ya conoces el problema. La manipulación de rutas de archivos basada en cadenas con os.path es verbosa, difícil de leer y propensa a errores. Concatenas cadenas y olvidas separadores. Hardcodeas / y el script se rompe en Windows. Encadenas cinco llamadas a os.path solo para obtener el nombre stem de un archivo, y tres meses después nadie puede leer el código -- incluyéndote a ti.
Estos no son casos extremos. Cada pipeline de ciencia de datos, cada aplicación web y cada script de automatización toca el sistema de archivos. Las rutas que funcionan en tu Mac fallan en la laptop Windows de un compañero. Las variables temporales de rutas se acumulan en tu código como deuda técnica. Y cuantas más llamadas os.path.join(os.path.dirname(...)) anides, más probable es que introduzcas un bug sutil que solo aparece en producción.
El módulo pathlib de Python resuelve esto. Introducido en Python 3.4 y completamente maduro desde Python 3.6, pathlib reemplaza la manipulación de rutas basada en cadenas con objetos Path adecuados. Las rutas se unen con el operador /. Los atributos de archivos como .name, .suffix y .stem son propiedades, no llamadas a funciones. Leer y escribir archivos toma una línea. Y todo funciona idénticamente a través de sistemas operativos. Esta guía cubre cada característica esencial de pathlib, desde la construcción básica de rutas hasta patrones avanzados para flujos de trabajo de ciencia de datos.
Por qué pathlib en lugar de os.path
Antes de pathlib, los desarrolladores de Python confiaban en os.path para operaciones de rutas y os para interacciones con el sistema de archivos. Ese enfoque funciona, pero trata las rutas como cadenas simples. Esto crea tres problemas persistentes:
-
La legibilidad se degrada rápido. Compara
os.path.splitext(os.path.basename(filepath))[0]conPath(filepath).stem. Ambos extraen el nombre del archivo sin su extensión. Uno es auto-documentado; el otro requiere análisis mental. -
Bugs multiplataforma. Hardcodear
/como separador o usar concatenación de cadenas significa que tu script de Linux se rompe silenciosamente en Windows.os.path.joinayuda, pero olvidar usarlo incluso una vez crea un bug latente. -
Funcionalidad dispersa. Para trabajar con rutas necesitas
os.pathpara descomposición,ospara creación de directorios,globpara coincidencia de patrones, yopen()para E/S de archivos.pathlibconsolida todo esto en un solo objetoPath.
Aquí está la misma tarea -- encontrar todos los archivos .csv en un directorio de datos y leer el primero -- en ambos estilos:
# enfoque con os.path
import os
import glob
data_dir = os.path.join(os.path.expanduser('~'), 'projects', 'data')
csv_files = glob.glob(os.path.join(data_dir, '**', '*.csv'), recursive=True)
if csv_files:
with open(csv_files[0], 'r') as f:
content = f.read()# enfoque con pathlib
from pathlib import Path
data_dir = Path.home() / 'projects' / 'data'
csv_files = list(data_dir.rglob('*.csv'))
if csv_files:
content = csv_files[0].read_text()La versión con pathlib es más corta, más fácil de leer, y hace exactamente lo mismo. Sin imports más allá de Path. Sin concatenación de cadenas. Sin llamada separada a open().
Creando Objetos Path
Cada operación con pathlib comienza con la creación de un objeto Path. La clase Path automáticamente retorna un PosixPath en Linux/macOS o un WindowsPath en Windows.
from pathlib import Path
# Desde una cadena
p = Path('/home/user/documents/report.csv')
# Desde múltiples segmentos (unidos automáticamente)
p = Path('home', 'user', 'documents', 'report.csv')
# Directorio de trabajo actual
cwd = Path.cwd()
print(cwd) # ej., /home/user/projects/myapp
# Directorio home del usuario
home = Path.home()
print(home) # ej., /home/user
# Ruta relativa
p = Path('data/output/results.csv')
# Desde una ruta existente
base = Path('/home/user')
full = Path(base, 'documents', 'file.txt')
print(full) # /home/user/documents/file.txtPath() sin argumentos retorna Path('.'), una ruta relativa al directorio actual. Usa Path.cwd() cuando necesites el directorio actual absoluto.
Uniendo Rutas con el Operador /
La característica más distintiva de pathlib es su operador / sobrecargado. En lugar de os.path.join(), encadenas segmentos de ruta con /:
from pathlib import Path
# Construir rutas naturalmente
project = Path.home() / 'projects' / 'analysis'
data_file = project / 'data' / 'sales_2026.csv'
print(data_file) # /home/user/projects/analysis/data/sales_2026.csv
# Mezclar objetos Path y cadenas
base = Path('/var/log')
app_log = base / 'myapp' / 'error.log'
print(app_log) # /var/log/myapp/error.log
# Combinar con variables
filename = 'report.pdf'
output = Path('output') / filename
print(output) # output/report.pdfEl operador / maneja separadores automáticamente. En Windows, Path('C:/Users') / 'data' produce C:\Users\data. Nunca necesitas pensar en / vs \ nuevamente.
También puedes usar joinpath() para el mismo resultado:
from pathlib import Path
# Equivalente a Path('data') / 'raw' / 'file.csv'
p = Path('data').joinpath('raw', 'file.csv')
print(p) # data/raw/file.csvComponentes de Path
Cada objeto Path expone sus componentes como propiedades. Sin llamadas a funciones, sin división de cadenas.
from pathlib import Path
p = Path('/home/user/projects/analysis/data/sales_report.final.csv')
print(p.name) # sales_report.final.csv (nombre de archivo con extensión)
print(p.stem) # sales_report.final (nombre de archivo sin la última extensión)
print(p.suffix) # .csv (última extensión)
print(p.suffixes) # ['.final', '.csv'] (todas las extensiones)
print(p.parent) # /home/user/projects/analysis/data
print(p.anchor) # / (raíz en Unix, C:\ en Windows)
print(p.parts) # ('/', 'home', 'user', 'projects', 'analysis', 'data', 'sales_report.final.csv')Navegando por los Parents
La propiedad .parent retorna el directorio padre inmediato. Encadénala para subir más:
from pathlib import Path
p = Path('/home/user/projects/analysis/data/output.csv')
print(p.parent) # /home/user/projects/analysis/data
print(p.parent.parent) # /home/user/projects/analysis
print(p.parent.parent.parent) # /home/user/projects
# .parents da acceso indexado a todos los ancestros
print(p.parents[0]) # /home/user/projects/analysis/data
print(p.parents[1]) # /home/user/projects/analysis
print(p.parents[2]) # /home/user/projects
print(p.parents[3]) # /home/userCambiando Componentes de la Ruta
Usa .with_name(), .with_stem(), y .with_suffix() para crear nuevas rutas con componentes modificados:
from pathlib import Path
p = Path('/data/reports/sales_q1.csv')
# Cambiar el nombre de archivo completamente
print(p.with_name('revenue_q1.csv')) # /data/reports/revenue_q1.csv
# Cambiar solo el stem (Python 3.9+)
print(p.with_stem('sales_q2')) # /data/reports/sales_q2.csv
# Cambiar solo la extensión
print(p.with_suffix('.parquet')) # /data/reports/sales_q1.parquet
# Eliminar la extensión
print(p.with_suffix('')) # /data/reports/sales_q1
# Agregar una extensión
backup = p.with_suffix(p.suffix + '.bak')
print(backup) # /data/reports/sales_q1.csv.bakEstos métodos retornan nuevos objetos Path. No renombran archivos en disco.
E/S de Archivos: Lectura y Escritura
pathlib elimina el boilerplate de open() / with para operaciones simples de archivos:
from pathlib import Path
file_path = Path('example.txt')
# Escribir texto a un archivo (crea si no existe, sobrescribe si existe)
file_path.write_text('Hello, pathlib!\nSecond line.')
# Leer texto de un archivo
content = file_path.read_text()
print(content)
# Hello, pathlib!
# Second line.
# Escribir bytes
binary_path = Path('data.bin')
binary_path.write_bytes(b'\x00\x01\x02\x03')
# Leer bytes
raw = binary_path.read_bytes()
print(raw) # b'\x00\x01\x02\x03'Especifica la codificación explícitamente cuando trabajes con texto no-ASCII:
from pathlib import Path
# Escribir texto UTF-8
Path('greeting.txt').write_text('こんにちは世界', encoding='utf-8')
# Leer con codificación
text = Path('greeting.txt').read_text(encoding='utf-8')
print(text) # こんにちは世界Para archivos grandes u operaciones de streaming, usa .open() que retorna un manejador de archivo igual que el open() incorporado:
from pathlib import Path
log_file = Path('application.log')
# Escribir línea por línea
with log_file.open('w') as f:
for i in range(1000):
f.write(f'Event {i}: processed\n')
# Leer línea por línea (eficiente en memoria para archivos grandes)
with log_file.open('r') as f:
for line in f:
if 'error' in line.lower():
print(line.strip())Operaciones de Directorio
Creando Directorios
from pathlib import Path
# Crear un solo directorio
Path('output').mkdir()
# Crear con parents (como os.makedirs)
Path('data/raw/2026/february').mkdir(parents=True, exist_ok=True)
# parents=True crea todos los directorios padre faltantes
# exist_ok=True previene error si el directorio ya existeUn error común es olvidar parents=True. Sin él, mkdir() lanza FileNotFoundError si falta algún directorio padre. Siempre usa parents=True cuando crees directorios anidados, y exist_ok=True para hacer la operación idempotente.
Listando Contenidos de Directorios
from pathlib import Path
project = Path('.')
# Listar todas las entradas (archivos y directorios)
for entry in project.iterdir():
print(entry.name, '(dir)' if entry.is_dir() else '(file)')
# Filtrar solo archivos
files = [f for f in project.iterdir() if f.is_file()]
print(f"Encontrados {len(files)} archivos")
# Filtrar solo directorios
dirs = [d for d in project.iterdir() if d.is_dir()]
print(f"Encontrados {len(dirs)} directorios")
# Ordenar por nombre
for entry in sorted(project.iterdir()):
print(entry.name)Eliminando Directorios y Archivos
from pathlib import Path
# Eliminar un archivo
Path('temp_output.csv').unlink()
# Eliminar un archivo solo si existe (Python 3.8+)
Path('temp_output.csv').unlink(missing_ok=True)
# Eliminar un directorio vacío
Path('empty_dir').rmdir()rmdir() solo elimina directorios vacíos. Para directorios no vacíos, usa shutil.rmtree():
from pathlib import Path
import shutil
target = Path('data/old_output')
if target.exists():
shutil.rmtree(target)Patrones Glob: Encontrando Archivos
pathlib tiene soporte glob incorporado. No necesitas importar el módulo glob por separado.
Glob Básico
from pathlib import Path
project = Path('/home/user/project')
# Encontrar todos los archivos Python en un directorio
for py_file in project.glob('*.py'):
print(py_file.name)
# Encontrar todos los archivos CSV
csv_files = list(project.glob('*.csv'))
print(f"Encontrados {len(csv_files)} archivos CSV")
# Encontrar archivos que coincidan con un patrón
reports = list(project.glob('report_*.xlsx'))Glob Recursivo con rglob
rglob() busca recursivamente a través de todos los subdirectorios. Es equivalente a glob('**/*.pattern') pero más conveniente:
from pathlib import Path
project = Path('/home/user/project')
# Encontrar todos los archivos Python en todos los subdirectorios
all_py = list(project.rglob('*.py'))
print(f"Encontrados {len(all_py)} archivos Python en todos los directorios")
# Encontrar todos los Jupyter notebooks recursivamente
notebooks = list(project.rglob('*.ipynb'))
for nb in notebooks:
print(f" {nb.relative_to(project)}")
# Encontrar todos los archivos de imagen
images = list(project.rglob('*.png')) + list(project.rglob('*.jpg'))
# Encontrar todos los archivos (sin filtro)
all_files = [f for f in project.rglob('*') if f.is_file()]Patrones Glob Avanzados
from pathlib import Path
data = Path('data')
# Comodín de un solo carácter
data.glob('file_?.csv') # file_1.csv, file_a.csv
# Rangos de caracteres
data.glob('report_202[456].csv') # report_2024.csv, report_2025.csv, report_2026.csv
# Cualquier nivel de subdirectorio
data.glob('**/output/*.csv') # data/raw/output/result.csv, data/processed/output/result.csv
# Múltiples extensiones (combinar dos globs)
from itertools import chain
all_data = chain(data.rglob('*.csv'), data.rglob('*.parquet'))Verificando Rutas
pathlib proporciona métodos booleanos claros para verificar el estado de las rutas:
from pathlib import Path
p = Path('/home/user/projects/data.csv')
# ¿Existe la ruta?
print(p.exists()) # True o False
# ¿Es un archivo?
print(p.is_file()) # True si existe y es un archivo regular
# ¿Es un directorio?
print(p.is_dir()) # True si existe y es un directorio
# ¿Es un enlace simbólico?
print(p.is_symlink()) # True si existe y es un symlink
# ¿Es una ruta absoluta?
print(p.is_absolute()) # True (/home/... empieza con raíz)
print(Path('data.csv').is_absolute()) # False (ruta relativa)Estos métodos nunca lanzan excepciones para rutas no existentes. Simplemente retornan False, lo que los hace seguros de usar en condicionales:
from pathlib import Path
config = Path('config.yaml')
if config.is_file():
settings = config.read_text()
else:
print("Archivo de configuración no encontrado, usando valores por defecto")Manipulación de Rutas
Resolviendo y Normalizando Rutas
from pathlib import Path
# Resolver a ruta absoluta (también resuelve symlinks)
p = Path('data/../data/./output.csv')
print(p.resolve()) # /home/user/project/data/output.csv
# Obtener ruta absoluta sin resolver symlinks
print(p.absolute()) # /home/user/project/data/../data/./output.csv
# Expandir directorio home del usuario
p = Path('~/Documents/report.csv')
print(p.expanduser()) # /home/user/Documents/report.csvRutas Relativas
from pathlib import Path
full_path = Path('/home/user/projects/analysis/data/output.csv')
base = Path('/home/user/projects')
# Obtener la ruta relativa desde base a full_path
relative = full_path.relative_to(base)
print(relative) # analysis/data/output.csv
# Esto lanza ValueError si la ruta no es relativa a la base
try:
Path('/var/log/app.log').relative_to(base)
except ValueError as e:
print(e) # '/var/log/app.log' is not relative to '/home/user/projects'
# Python 3.12+: verificación is_relative_to()
print(full_path.is_relative_to(base)) # True
print(Path('/var/log').is_relative_to(base)) # FalseMetadatos de Archivos y Stat
from pathlib import Path
from datetime import datetime
p = Path('data.csv')
# Obtener estadísticas del archivo
stat = p.stat()
print(f"Tamaño: {stat.st_size} bytes")
print(f"Modificado: {datetime.fromtimestamp(stat.st_mtime)}")
print(f"Creado: {datetime.fromtimestamp(stat.st_ctime)}")
# Conveniencia: obtener tamaño directamente (a través de stat)
size_mb = p.stat().st_size / (1024 * 1024)
print(f"Tamaño: {size_mb:.2f} MB")
# Verificar si dos rutas apuntan al mismo archivo
p1 = Path('/home/user/data.csv')
p2 = Path.home() / 'data.csv'
print(p1.samefile(p2)) # True (si resuelven al mismo archivo)Renombrando y Moviendo Archivos
from pathlib import Path
# Renombrar un archivo (retorna el nuevo Path)
old = Path('report_draft.csv')
new = old.rename('report_final.csv')
print(new) # report_final.csv
# Mover a un directorio diferente
source = Path('output/temp_results.csv')
dest = source.rename(Path('archive') / source.name)
# Reemplazar un archivo (sobrescribe si el destino existe)
Path('new_data.csv').replace('data.csv')Nota: .rename() sobrescribirá el archivo de destino en Unix pero puede lanzar un error en Windows. Usa .replace() para un comportamiento de sobrescritura garantizado multiplataforma.
os.path vs pathlib: Comparación Completa
Aquí hay una tabla de referencia mapeando cada operación común de os.path a su equivalente en pathlib:
| Operación | os.path / os | pathlib |
|---|---|---|
| Unir rutas | os.path.join('a', 'b') | Path('a') / 'b' |
| Directorio actual | os.getcwd() | Path.cwd() |
| Directorio home | os.path.expanduser('~') | Path.home() |
| Ruta absoluta | os.path.abspath(p) | Path(p).resolve() |
| Nombre de archivo | os.path.basename(p) | Path(p).name |
| Directorio | os.path.dirname(p) | Path(p).parent |
| Extensión | os.path.splitext(p)[1] | Path(p).suffix |
| Stem (nombre sin ext) | os.path.splitext(os.path.basename(p))[0] | Path(p).stem |
| Existe | os.path.exists(p) | Path(p).exists() |
| Es archivo | os.path.isfile(p) | Path(p).is_file() |
| Es directorio | os.path.isdir(p) | Path(p).is_dir() |
| Es symlink | os.path.islink(p) | Path(p).is_symlink() |
| Es absoluto | os.path.isabs(p) | Path(p).is_absolute() |
| Tamaño de archivo | os.path.getsize(p) | Path(p).stat().st_size |
| Listar directorio | os.listdir(p) | Path(p).iterdir() |
| Crear directorio | os.makedirs(p, exist_ok=True) | Path(p).mkdir(parents=True, exist_ok=True) |
| Eliminar archivo | os.remove(p) | Path(p).unlink() |
| Eliminar directorio | os.rmdir(p) | Path(p).rmdir() |
| Renombrar | os.rename(old, new) | Path(old).rename(new) |
| Leer archivo | open(p).read() | Path(p).read_text() |
| Escribir archivo | open(p, 'w').write(text) | Path(p).write_text(text) |
| Glob | glob.glob('*.py') | Path('.').glob('*.py') |
| Glob recursivo | glob.glob('**/*.py', recursive=True) | Path('.').rglob('*.py') |
| Expandir usuario | os.path.expanduser(p) | Path(p).expanduser() |
| Ruta relativa | os.path.relpath(p, base) | Path(p).relative_to(base) |
Trabajando con Archivos Temporales
pathlib se integra limpiamente con el módulo tempfile de Python:
from pathlib import Path
import tempfile
# Crear un directorio temporal como Path
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_path = Path(tmp_dir)
# Escribir archivos temporales usando pathlib
data_file = tmp_path / 'intermediate_results.csv'
data_file.write_text('col1,col2\n1,2\n3,4\n')
config_file = tmp_path / 'run_config.json'
config_file.write_text('{"epochs": 100, "lr": 0.001}')
# Listar lo que creamos
for f in tmp_path.iterdir():
print(f"{f.name}: {f.stat().st_size} bytes")
# Procesar archivos...
print(data_file.read_text())
# El directorio y todos los archivos se eliminan automáticamente aquífrom pathlib import Path
import tempfile
# Crear un archivo temporal nombrado
tmp = tempfile.NamedTemporaryFile(suffix='.csv', delete=False)
tmp_path = Path(tmp.name)
tmp.close()
# Usar pathlib para escribir en él
tmp_path.write_text('id,value\n1,100\n2,200\n')
print(f"Archivo temp en: {tmp_path}")
# Limpiar cuando termines
tmp_path.unlink()Pathlib en Flujos de Trabajo de Ciencia de Datos
Los proyectos de ciencia de datos típicamente involucran leer datasets de múltiples directorios, crear carpetas de salida para resultados, y gestionar artefactos de experimentos. pathlib hace estos patrones limpios y confiables.
Organizando Directorios de Proyecto
from pathlib import Path
def setup_experiment(experiment_name):
"""Crea una estructura estándar de directorios de experimento."""
base = Path('experiments') / experiment_name
dirs = ['data/raw', 'data/processed', 'models', 'results/figures', 'results/tables', 'logs']
for d in dirs:
(base / d).mkdir(parents=True, exist_ok=True)
# Crear un archivo de configuración
config = base / 'config.json'
if not config.exists():
config.write_text('{"learning_rate": 0.001, "epochs": 50}')
print(f"Directorio de experimento listo: {base.resolve()}")
return base
project = setup_experiment('sales_forecast_v2')Leyendo Múltiples Archivos de Datos
from pathlib import Path
import pandas as pd
data_dir = Path('data/raw')
# Leer todos los archivos CSV en un solo DataFrame
dfs = []
for csv_file in sorted(data_dir.glob('*.csv')):
print(f"Cargando {csv_file.name}...")
df = pd.read_csv(csv_file)
df['source_file'] = csv_file.stem # Agregar nombre de archivo fuente
dfs.append(df)
combined = pd.concat(dfs, ignore_index=True)
print(f"Cargadas {len(combined)} filas desde {len(dfs)} archivos")
# Guardar en directorio procesado
output_path = Path('data/processed') / 'combined_sales.parquet'
output_path.parent.mkdir(parents=True, exist_ok=True)
combined.to_parquet(output_path)Después de cargar tus datos CSV con pathlib, puedes explorarlos visualmente con PyGWalker (opens in a new tab). Convierte cualquier DataFrame de Pandas en una interfaz interactiva tipo Tableau para exploración de datos drag-and-drop -- sin código extra requerido.
Guardando Resultados de Experimentos
from pathlib import Path
from datetime import datetime
import json
def save_results(metrics, experiment_dir):
"""Guarda métricas de experimento con timestamp."""
results_dir = Path(experiment_dir) / 'results'
results_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_file = results_dir / f'metrics_{timestamp}.json'
output_file.write_text(json.dumps(metrics, indent=2))
print(f"Resultados guardados en {output_file}")
return output_file
# Uso
metrics = {'accuracy': 0.94, 'f1_score': 0.91, 'loss': 0.187}
save_results(metrics, 'experiments/sales_forecast_v2')Gestionando Rutas de Archivos en Notebooks
Cuando trabajas en Jupyter notebooks, las rutas a menudo se rompen porque el directorio de trabajo del notebook puede diferir de la raíz del proyecto. pathlib hace esto fácil de manejar:
from pathlib import Path
# Siempre resolver a ruta absoluta desde la ubicación del notebook
NOTEBOOK_DIR = Path.cwd()
PROJECT_ROOT = NOTEBOOK_DIR.parent # si el notebook está en notebooks/
DATA_DIR = PROJECT_ROOT / 'data'
OUTPUT_DIR = PROJECT_ROOT / 'output'
# Ahora todas las rutas son absolutas y confiables
train_data = DATA_DIR / 'train.csv'
print(f"Cargando: {train_data}")
assert train_data.exists(), f"Falta: {train_data}"Si trabajas extensivamente en Jupyter y quieres un entorno potenciado por IA que ayude a gestionar archivos de proyecto y rutas de datos, RunCell (opens in a new tab) agrega una capa de agente IA a tu notebook. Describe lo que necesitas -- "encuentra todos los archivos Parquet en el directorio de datos y carga el más reciente" -- y genera el código pathlib y lo ejecuta por ti.
Patrones Comunes y Recetas
Escritura Segura de Archivos con Reemplazo Atómico
Previene corrupción de datos escribiendo primero en un archivo temporal, luego reemplazando atómicamente el destino:
from pathlib import Path
import tempfile
def safe_write(target_path, content):
"""Escribe contenido a archivo atómicamente para prevenir corrupción."""
target = Path(target_path)
target.parent.mkdir(parents=True, exist_ok=True)
# Escribir a archivo temp en el mismo directorio
tmp = tempfile.NamedTemporaryFile(
mode='w', dir=target.parent, suffix='.tmp', delete=False
)
tmp_path = Path(tmp.name)
try:
tmp.write(content)
tmp.close()
tmp_path.replace(target) # Atómico en la mayoría de sistemas de archivos
except Exception:
tmp_path.unlink(missing_ok=True)
raise
safe_write('config/settings.json', '{"debug": true}')Renombrado Masivo de Archivos
from pathlib import Path
photos_dir = Path('photos')
# Renombrar todos los archivos .jpeg a .jpg
for f in photos_dir.glob('*.jpeg'):
f.rename(f.with_suffix('.jpg'))
# Agregar prefijo a todos los archivos
for i, f in enumerate(sorted(photos_dir.glob('*.jpg')), start=1):
new_name = f.parent / f'photo_{i:04d}{f.suffix}'
f.rename(new_name)Encontrar Archivos Duplicados por Tamaño
from pathlib import Path
from collections import defaultdict
def find_potential_duplicates(directory):
"""Encuentra archivos con tamaños idénticos (duplicados potenciales)."""
size_map = defaultdict(list)
for f in Path(directory).rglob('*'):
if f.is_file():
size_map[f.stat().st_size].append(f)
# Retornar solo grupos con más de un archivo
return {size: files for size, files in size_map.items() if len(files) > 1}
dupes = find_potential_duplicates('data')
for size, files in dupes.items():
print(f"\n{size} bytes:")
for f in files:
print(f" {f}")Construir una Visualización de Árbol de Archivos
from pathlib import Path
def tree(directory, prefix='', max_depth=3, _depth=0):
"""Imprime una estructura de árbol de un directorio."""
if _depth >= max_depth:
return
path = Path(directory)
entries = sorted(path.iterdir(), key=lambda e: (e.is_file(), e.name))
for i, entry in enumerate(entries):
is_last = (i == len(entries) - 1)
connector = '└── ' if is_last else '├── '
print(f'{prefix}{connector}{entry.name}')
if entry.is_dir():
extension = ' ' if is_last else '│ '
tree(entry, prefix + extension, max_depth, _depth + 1)
tree('my_project', max_depth=3)Salida:
├── data
│ ├── processed
│ │ └── combined.csv
│ └── raw
│ ├── sales_2025.csv
│ └── sales_2026.csv
├── notebooks
│ └── analysis.ipynb
├── output
│ └── figures
└── requirements.txtErrores Comunes y Cómo Evitarlos
Error 1: Comparar Cadenas con Objetos Path
from pathlib import Path
p = Path('data/output.csv')
# INCORRECTO: Comparar cadena con Path
if p == 'data/output.csv': # Puede funcionar pero es frágil
print("Coincidencia")
# CORRECTO: Comparar Path con Path, o usar str()
if p == Path('data/output.csv'):
print("Coincidencia")
# CORRECTO: Convertir a cadena si es necesario
if str(p) == 'data/output.csv':
print("Coincidencia")Error 2: Olvidar parents=True en mkdir
from pathlib import Path
# INCORRECTO: Lanza FileNotFoundError si 'data' no existe
# Path('data/raw/2026').mkdir()
# CORRECTO: Crear todos los padres faltantes
Path('data/raw/2026').mkdir(parents=True, exist_ok=True)Error 3: Usar Concatenación de Cadenas en lugar de /
from pathlib import Path
base = Path('/home/user')
# INCORRECTO: La concatenación de cadenas rompe pathlib
# bad = base + '/data/file.csv' # TypeError
# CORRECTO: Usar el operador /
good = base / 'data' / 'file.csv'Error 4: Pasar Path a Librerías Que Esperan Cadenas
La mayoría de las librerías modernas (Pandas, NumPy, PIL, etc.) aceptan objetos Path nativamente. Pero si encuentras una librería más antigua que requiere cadenas, convierte explícitamente:
from pathlib import Path
p = Path('data/output.csv')
# La mayoría de las librerías aceptan Path directamente
import pandas as pd
df = pd.read_csv(p) # Funciona bien
# Para librerías más antiguas que necesitan cadenas
import some_legacy_lib
some_legacy_lib.process(str(p)) # Convertir con str()
# os.fspath() también funciona (Python 3.6+)
import os
some_legacy_lib.process(os.fspath(p))Error 5: Usar Rutas Hardcodeadas
from pathlib import Path
# INCORRECTO: Ruta absoluta hardcodeada
# data_path = Path('/home/alice/project/data/sales.csv')
# CORRECTO: Construir desde componentes relativos o dinámicos
data_path = Path.cwd() / 'data' / 'sales.csv'
# CORRECTO: Construir desde directorio home
config_path = Path.home() / '.config' / 'myapp' / 'settings.json'
# CORRECTO: Construir desde variable de entorno
import os
data_root = Path(os.getenv('DATA_DIR', 'data'))
data_path = data_root / 'sales.csv'Preguntas Frecuentes
¿Qué es pathlib en Python?
pathlib es un módulo de la biblioteca estándar (introducido en Python 3.4) que proporciona clases orientadas a objetos para trabajar con rutas del sistema de archivos. En lugar de tratar las rutas como cadenas y usar funciones como os.path.join(), creas objetos Path y usas métodos y operadores. Maneja las diferencias de rutas multiplataforma automáticamente.
¿Cuándo debería usar pathlib en lugar de os.path?
Usa pathlib para todos los proyectos nuevos de Python 3.6+. Produce código más limpio, más legible, consolida las operaciones de rutas en un solo objeto, y maneja los problemas multiplataforma automáticamente. La única razón para usar os.path es mantener código legacy que debe soportar Python 2, o usar las pocas funciones de os que no tienen equivalente en pathlib (como os.environ para variables de entorno).
¿Funciona pathlib en Windows?
Sí. pathlib automáticamente usa objetos WindowsPath en Windows y PosixPath en Linux/macOS. El operador / produce rutas separadas por backslash en Windows. Escribirás el mismo código en todas las plataformas y pathlib manejará las diferencias.
¿Puedo usar objetos Path con Pandas?
Sí. Desde Python 3.6 y Pandas 0.21+, puedes pasar objetos Path directamente a pd.read_csv(), pd.read_excel(), df.to_csv(), y otras funciones de E/S. No se necesita conversión con str().
¿Cuál es la diferencia entre Path.resolve() y Path.absolute()?
.resolve() retorna la ruta absoluta y también resuelve cualquier enlace simbólico y componentes ../.. .absolute() retorna la ruta absoluta sin resolver symlinks o normalizar la ruta. En la mayoría de los casos, .resolve() es lo que quieres.
¿Cómo convierto entre objetos Path y cadenas?
Usa str(path) para convertir un Path a cadena. Usa Path(string) para crear un Path desde una cadena. También puedes usar os.fspath(path) para conversión explícita a cadena. La mayoría de las librerías modernas de Python aceptan objetos Path directamente, así que la conversión raramente es necesaria.
Conclusión
El módulo pathlib de Python es el estándar moderno para la manipulación de rutas de archivos. El operador / hace que la unión de rutas sea legible. Las propiedades como .name, .stem, .suffix, y .parent eliminan cadenas verbosas de funciones os.path. Los métodos incorporados para leer, escribir, crear directorios, y hacer glob consolidan lo que solía requerir os, os.path, glob, y open() en una sola API consistente.
La migración de os.path a pathlib es directa: reemplaza os.path.join() con /, reemplaza os.path.exists() con .exists(), reemplaza os.makedirs() con .mkdir(parents=True), y reemplaza glob.glob() con .glob() o .rglob(). Cada librería importante de Python -- Pandas, NumPy, PIL, PyTorch -- ahora acepta objetos Path nativamente. No hay razón para evitarlo en proyectos nuevos.
Empieza pequeño. Elige un script que tenga código os.path desordenado. Reemplaza las operaciones de rutas con pathlib. El código será más corto, más legible, y más portable. Luego haz lo mismo con el siguiente script.