Python

Python

Tutorial Python: Módulo pathlib

Aprende a usar el módulo pathlib en Python para manipular rutas y archivos con objetos, mejorando la portabilidad y legibilidad del código.

Aprende Python y certifícate

Rutas como objetos

En Python, tradicionalmente hemos manipulado rutas de archivos y directorios como simples cadenas de texto. Sin embargo, este enfoque presenta varios inconvenientes: concatenar rutas correctamente entre diferentes sistemas operativos, manejar barras separadoras, y realizar operaciones comunes como verificar la existencia de archivos requiere código adicional y propenso a errores.

El módulo pathlib, introducido en Python 3.4 y mejorado significativamente en versiones posteriores, ofrece una solución elegante a estos problemas al representar las rutas como objetos en lugar de cadenas. Este enfoque orientado a objetos transforma completamente la forma en que trabajamos con el sistema de archivos.

Conceptos básicos de pathlib

Para empezar a utilizar pathlib, primero debemos importarlo:

from pathlib import Path

La clase Path es el componente central de pathlib. Cuando creamos un objeto Path, obtenemos una representación de una ruta del sistema de archivos que podemos manipular mediante métodos y propiedades:

# Crear un objeto Path para el directorio actual
ruta_actual = Path('.')
print(ruta_actual)  # Muestra: .

# Crear un objeto Path para una ruta específica
ruta_documento = Path('/home/usuario/documentos/archivo.txt')
print(ruta_documento)  # Muestra: /home/usuario/documentos/archivo.txt

Una ventaja inmediata es que pathlib detecta automáticamente el sistema operativo y utiliza el separador de rutas adecuado (barra diagonal / en Unix/Linux/macOS o barra invertida \ en Windows).

Creación de rutas

Podemos crear objetos Path de varias maneras:

# Ruta absoluta
ruta1 = Path('/home/usuario/documentos')

# Ruta relativa
ruta2 = Path('documentos/archivos')

# Desde partes de ruta
ruta3 = Path('home', 'usuario', 'documentos')

Una característica poderosa de pathlib es la capacidad de construir rutas mediante el operador /:

# Construir una ruta combinando objetos Path
base = Path('/home/usuario')
completa = base / 'documentos' / 'archivo.txt'
print(completa)  # Muestra: /home/usuario/documentos/archivo.txt

Este enfoque es mucho más legible y menos propenso a errores que concatenar cadenas o usar os.path.join().

Acceso a componentes de la ruta

Los objetos Path proporcionan propiedades para acceder a diferentes componentes de una ruta:

ruta = Path('/home/usuario/documentos/informe.txt')

# Obtener el nombre del archivo
print(ruta.name)  # Muestra: informe.txt

# Obtener la extensión del archivo
print(ruta.suffix)  # Muestra: .txt

# Obtener el nombre sin extensión
print(ruta.stem)  # Muestra: informe

# Obtener el directorio padre
print(ruta.parent)  # Muestra: /home/usuario/documentos

También podemos acceder a múltiples niveles de directorios padres:

# Obtener el padre del padre (dos niveles arriba)
print(ruta.parent.parent)  # Muestra: /home/usuario

Resolución de rutas

Los objetos Path pueden resolver rutas relativas y absolutas:

# Convertir una ruta relativa en absoluta
ruta_relativa = Path('documentos/archivo.txt')
ruta_absoluta = ruta_relativa.absolute()
print(ruta_absoluta)  # Muestra la ruta absoluta completa

# Resolver una ruta (elimina componentes como '..' y '.')
ruta_confusa = Path('/home/usuario/../usuario/./documentos')
ruta_resuelta = ruta_confusa.resolve()
print(ruta_resuelta)  # Muestra: /home/usuario/documentos

Rutas especiales

Pathlib facilita el acceso a rutas especiales del sistema:

# Directorio de inicio del usuario (home)
home = Path.home()
print(home)  # Muestra: /home/usuario (en Linux/macOS) o C:\Users\usuario (en Windows)

# Directorio de trabajo actual
actual = Path.cwd()
print(actual)  # Muestra el directorio desde donde se ejecuta el script

Propiedades de los objetos Path

Los objetos Path tienen propiedades que nos permiten consultar información sobre la ruta:

ruta = Path('/home/usuario/documentos/archivo.txt')

# Verificar si la ruta es absoluta
print(ruta.is_absolute())  # Muestra: True

# Verificar si la ruta apunta a un archivo (si existe)
print(Path('/etc/passwd').is_file())  # Muestra: True si el archivo existe

# Verificar si la ruta apunta a un directorio (si existe)
print(Path('/home').is_dir())  # Muestra: True si el directorio existe

# Verificar si la ruta existe
print(ruta.exists())  # Muestra: True si existe, False si no

Iteración sobre directorios

Una ventaja significativa de pathlib es la facilidad para iterar sobre el contenido de directorios:

# Listar todos los archivos y directorios en una ruta
for item in Path('.').iterdir():
    print(item)

# Listar solo archivos Python en el directorio actual
for archivo_py in Path('.').glob('*.py'):
    print(archivo_py)

# Búsqueda recursiva de archivos
for archivo_txt in Path('.').rglob('*.txt'):
    print(archivo_txt)

El método glob() permite filtrar archivos usando patrones, mientras que rglob() realiza una búsqueda recursiva en subdirectorios.

Compatibilidad entre sistemas operativos

Una de las mayores ventajas de pathlib es su manejo transparente de las diferencias entre sistemas operativos:

# Esta ruta funcionará correctamente en Windows, macOS y Linux
ruta = Path('documentos') / 'archivos' / 'datos.csv'

Pathlib se encarga automáticamente de usar el separador correcto según el sistema operativo, lo que hace que nuestro código sea más portable.

Operaciones con rutas

Una vez que hemos creado objetos Path, podemos realizar numerosas operaciones con ellos sin necesidad de importar módulos adicionales. El módulo pathlib proporciona métodos intuitivos que simplifican tareas comunes de manipulación de archivos y directorios.

Comprobación de existencia y tipo

Antes de realizar operaciones sobre archivos o directorios, a menudo necesitamos verificar su existencia y tipo:

from pathlib import Path

ruta = Path('datos/config.ini')

# Comprobar si existe
if ruta.exists():
    print("La ruta existe")
    
# Comprobar el tipo de la ruta
if ruta.is_file():
    print("Es un archivo")
elif ruta.is_dir():
    print("Es un directorio")
elif ruta.is_symlink():
    print("Es un enlace simbólico")

También podemos verificar permisos específicos:

# Comprobar si se puede leer
if ruta.is_file() and os.access(ruta, os.R_OK):
    print("El archivo se puede leer")
    
# Comprobar si se puede ejecutar
if ruta.is_file() and ruta.with_suffix('.py').exists() and os.access(ruta, os.X_OK):
    print("El archivo Python se puede ejecutar")

Modificación de rutas

Pathlib ofrece métodos para modificar rutas sin alterar los archivos subyacentes:

ruta = Path('/home/usuario/documentos/informe.txt')

# Cambiar la extensión
nueva_ruta = ruta.with_suffix('.pdf')
print(nueva_ruta)  # Muestra: /home/usuario/documentos/informe.pdf

# Cambiar el nombre manteniendo el directorio
otra_ruta = ruta.with_name('reporte.txt')
print(otra_ruta)  # Muestra: /home/usuario/documentos/reporte.txt

# Cambiar solo una parte del nombre (stem)
ruta_modificada = ruta.with_stem('informe_final')
print(ruta_modificada)  # Muestra: /home/usuario/documentos/informe_final.txt

Operaciones de lectura y escritura

Los objetos Path incluyen métodos para leer y escribir archivos directamente:

# Leer todo el contenido como texto
ruta = Path('config.ini')
contenido = ruta.read_text(encoding='utf-8')
print(contenido)

# Leer todo el contenido como bytes
datos_binarios = ruta.read_bytes()

# Escribir texto a un archivo
ruta_salida = Path('salida.txt')
ruta_salida.write_text('Contenido nuevo', encoding='utf-8')

# Escribir bytes a un archivo
ruta_binaria = Path('datos.bin')
ruta_binaria.write_bytes(b'\x00\x01\x02\x03')

Estos métodos son perfectos para operaciones simples. Para casos más complejos, podemos usar los objetos Path con las funciones tradicionales de apertura de archivos:

# Usar Path con open()
with open(ruta, 'r', encoding='utf-8') as f:
    for linea in f:
        print(linea.strip())

Creación y eliminación de archivos y directorios

Pathlib facilita la creación y eliminación de elementos del sistema de archivos:

# Crear un directorio
directorio = Path('nuevos_datos')
directorio.mkdir(exist_ok=True)  # No falla si ya existe

# Crear directorios anidados
ruta_anidada = Path('proyectos/python/scripts')
ruta_anidada.mkdir(parents=True, exist_ok=True)

# Crear un archivo vacío o actualizar su timestamp
archivo = Path('archivo_vacio.txt')
archivo.touch()

# Eliminar un archivo
if archivo.exists():
    archivo.unlink()

# Eliminar un directorio (debe estar vacío)
if directorio.exists() and directorio.is_dir():
    directorio.rmdir()

Para operaciones más complejas como eliminar directorios no vacíos, podemos combinar pathlib con otras funciones:

import shutil

# Eliminar un directorio y todo su contenido
directorio_completo = Path('directorio_con_archivos')
if directorio_completo.exists():
    shutil.rmtree(directorio_completo)

Renombrar y mover archivos

Pathlib permite renombrar y mover archivos o directorios de forma intuitiva:

# Renombrar un archivo
origen = Path('documento_viejo.txt')
destino = Path('documento_nuevo.txt')
if origen.exists():
    origen.rename(destino)

# Mover un archivo a otro directorio
archivo = Path('informe.pdf')
nuevo_directorio = Path('archivados')
if nuevo_directorio.exists() and nuevo_directorio.is_dir():
    archivo.rename(nuevo_directorio / archivo.name)

Copia de archivos

Aunque pathlib no incluye métodos directos para copiar archivos, podemos combinarlo fácilmente con el módulo shutil:

import shutil

# Copiar un archivo
origen = Path('datos.csv')
destino = Path('respaldo/datos.csv')

# Asegurar que el directorio destino existe
destino.parent.mkdir(parents=True, exist_ok=True)

# Realizar la copia
if origen.exists():
    shutil.copy2(origen, destino)  # copy2 preserva metadatos

Búsqueda de archivos

Los métodos de búsqueda de pathlib son muy potentes para encontrar archivos que cumplan ciertos criterios:

# Encontrar todos los archivos Python en el directorio actual
archivos_py = list(Path('.').glob('*.py'))

# Búsqueda recursiva de archivos de configuración
archivos_config = list(Path('.').rglob('*.ini'))

# Búsqueda con patrones más complejos
imagenes = list(Path('documentos').glob('**/*.{jpg,png,gif}'))

# Filtrar resultados con comprensión de listas
archivos_grandes = [
    archivo for archivo in Path('.').rglob('*.txt') 
    if archivo.is_file() and archivo.stat().st_size > 1000000
]

Obtención de metadatos

Podemos acceder a información detallada sobre archivos y directorios:

archivo = Path('documento.pdf')
if archivo.exists():
    # Obtener estadísticas del archivo
    stats = archivo.stat()
    
    # Tamaño en bytes
    print(f"Tamaño: {stats.st_size} bytes")
    
    # Fechas de acceso, modificación y creación
    from datetime import datetime
    print(f"Último acceso: {datetime.fromtimestamp(stats.st_atime)}")
    print(f"Última modificación: {datetime.fromtimestamp(stats.st_mtime)}")
    print(f"Creación: {datetime.fromtimestamp(stats.st_ctime)}")
    
    # Permisos (en formato octal)
    print(f"Permisos: {oct(stats.st_mode)[-3:]}")

Operaciones con rutas relativas

Pathlib facilita el trabajo con rutas relativas entre diferentes ubicaciones:

# Obtener la ruta relativa desde una ubicación a otra
base = Path('/home/usuario')
destino = Path('/home/usuario/proyectos/python/script.py')
ruta_relativa = destino.relative_to(base)
print(ruta_relativa)  # Muestra: proyectos/python/script.py

# Comprobar si una ruta es subpath de otra
try:
    Path('/tmp/archivo.txt').relative_to('/home')
    print("Es un subpath")
except ValueError:
    print("No es un subpath")  # Este será el resultado

Comparación de rutas

Los objetos Path se pueden comparar directamente, lo que facilita verificar si dos rutas apuntan al mismo lugar:

ruta1 = Path('/home/usuario/../usuario/documentos')
ruta2 = Path('/home/usuario/documentos')

# Comparación directa (sin resolver)
print(ruta1 == ruta2)  # Muestra: False

# Comparación después de resolver
print(ruta1.resolve() == ruta2.resolve())  # Muestra: True

Conversión entre Path y string

A veces necesitamos convertir entre objetos Path y cadenas, especialmente al interactuar con APIs que no soportan pathlib:

# Convertir Path a string
ruta = Path('/home/usuario/archivo.txt')
ruta_str = str(ruta)
print(ruta_str)  # Muestra: /home/usuario/archivo.txt

# Convertir string a Path
path_de_nuevo = Path(ruta_str)

Estas operaciones con rutas demuestran cómo pathlib simplifica significativamente el trabajo con el sistema de archivos, proporcionando una interfaz orientada a objetos que es más intuitiva y menos propensa a errores que las soluciones tradicionales basadas en cadenas.

Ventajas sobre os.path

El módulo pathlib representa una evolución significativa respecto al tradicional os.path que ha sido durante décadas la forma estándar de manipular rutas en Python. Aunque ambos cumplen propósitos similares, pathlib ofrece numerosas ventajas que lo convierten en la opción preferida para el desarrollo moderno en Python.

Sintaxis más clara y concisa

La diferencia más evidente entre ambos enfoques es la sintaxis. Mientras que os.path requiere llamadas a funciones separadas para cada operación, pathlib utiliza un enfoque orientado a objetos con métodos encadenables:

# Enfoque tradicional con os.path
import os
ruta_completa = os.path.join(os.path.expanduser('~'), 'documentos', 'archivo.txt')
nombre = os.path.basename(ruta_completa)
extension = os.path.splitext(nombre)[1]

# Enfoque moderno con pathlib
from pathlib import Path
ruta_completa = Path.home() / 'documentos' / 'archivo.txt'
nombre = ruta_completa.name
extension = ruta_completa.suffix

Esta sintaxis más fluida de pathlib no solo reduce la cantidad de código, sino que también mejora significativamente su legibilidad.

Operador de división para construir rutas

Una de las características más elegantes de pathlib es el uso del operador / para construir rutas:

# Con os.path (propenso a errores)
ruta = os.path.join(os.path.join('directorio', 'subdirectorio'), 'archivo.txt')

# Con pathlib (intuitivo y claro)
ruta = Path('directorio') / 'subdirectorio' / 'archivo.txt'

Este enfoque es más natural, ya que refleja la notación habitual de rutas en sistemas Unix, pero funciona correctamente en todos los sistemas operativos.

Métodos integrados para operaciones comunes

Con os.path, las operaciones de sistema de archivos requieren importar y combinar múltiples módulos:

# Enfoque tradicional con múltiples módulos
import os
import shutil
import glob

# Verificar existencia
if os.path.exists(ruta) and os.path.isfile(ruta):
    # Leer contenido
    with open(ruta, 'r') as f:
        contenido = f.read()
    
    # Buscar archivos
    archivos_txt = glob.glob(os.path.join(directorio, '*.txt'))

Con pathlib, estas operaciones están integradas en la misma interfaz:

# Enfoque unificado con pathlib
from pathlib import Path

ruta = Path('archivo.txt')
# Verificar existencia
if ruta.exists() and ruta.is_file():
    # Leer contenido
    contenido = ruta.read_text()
    
    # Buscar archivos
    archivos_txt = list(Path(directorio).glob('*.txt'))

Esta integración reduce la necesidad de importar múltiples módulos y proporciona una API más coherente.

Compatibilidad entre sistemas operativos

Aunque os.path intenta manejar las diferencias entre sistemas operativos, pathlib lo hace de manera más transparente:

# os.path requiere cuidado con los separadores
windows_path = 'C:\\Users\\usuario\\documentos'
unix_path = '/home/usuario/documentos'

# pathlib maneja esto automáticamente
windows_path = Path('C:/Users/usuario/documentos')  # Funciona en Windows
unix_path = Path('/home/usuario/documentos')        # Funciona en Unix/Linux

Pathlib normaliza automáticamente los separadores según el sistema operativo, lo que hace que el código sea más portable sin esfuerzo adicional.

Métodos específicos para componentes de ruta

Con os.path, extraer componentes de una ruta requiere múltiples llamadas a funciones:

# Extraer componentes con os.path
ruta = '/home/usuario/documentos/informe.txt'
directorio = os.path.dirname(ruta)
nombre = os.path.basename(ruta)
nombre_sin_ext = os.path.splitext(nombre)[0]
extension = os.path.splitext(nombre)[1]

Pathlib proporciona propiedades específicas para cada componente:

# Extraer componentes con pathlib
ruta = Path('/home/usuario/documentos/informe.txt')
directorio = ruta.parent
nombre = ruta.name
nombre_sin_ext = ruta.stem
extension = ruta.suffix

Este enfoque es más intuitivo y menos propenso a errores.

Operaciones de E/S integradas

Una ventaja significativa de pathlib es la inclusión de métodos para operaciones de entrada/salida:

# Con os.path y funciones estándar
import os

ruta = 'datos.txt'
# Leer
with open(ruta, 'r', encoding='utf-8') as f:
    contenido = f.read()
    
# Escribir
with open(ruta, 'w', encoding='utf-8') as f:
    f.write('Nuevos datos')

# Con pathlib
from pathlib import Path

ruta = Path('datos.txt')
# Leer
contenido = ruta.read_text(encoding='utf-8')
    
# Escribir
ruta.write_text('Nuevos datos', encoding='utf-8')

Esta integración elimina la necesidad de usar bloques with para operaciones simples de lectura y escritura.

Iteración y filtrado de directorios

La búsqueda y filtrado de archivos es mucho más elegante con pathlib:

# Con os y glob
import os
import glob

# Listar archivos
archivos = os.listdir('.')
# Filtrar por extensión
archivos_py = glob.glob('*.py')
# Búsqueda recursiva
archivos_txt_recursivos = glob.glob('**/*.txt', recursive=True)

# Con pathlib
from pathlib import Path

# Listar archivos
archivos = list(Path('.').iterdir())
# Filtrar por extensión
archivos_py = list(Path('.').glob('*.py'))
# Búsqueda recursiva
archivos_txt_recursivos = list(Path('.').rglob('*.txt'))

Los métodos iterdir(), glob() y rglob() de pathlib proporcionan una forma más coherente y potente de trabajar con colecciones de archivos.

Rendimiento y optimización

En versiones recientes de Python, pathlib ha sido optimizado para ofrecer un rendimiento comparable o incluso superior a os.path en muchos casos:

# Ejemplo de rendimiento: verificar existencia de múltiples archivos
import time
import os
from pathlib import Path

# Preparar lista de rutas
rutas = [f'archivo_{i}.txt' for i in range(1000)]

# Medir con os.path
inicio = time.time()
for ruta in rutas:
    os.path.exists(ruta)
tiempo_os = time.time() - inicio

# Medir con pathlib
inicio = time.time()
for ruta in rutas:
    Path(ruta).exists()
tiempo_pathlib = time.time() - inicio

print(f"Tiempo os.path: {tiempo_os:.6f}s")
print(f"Tiempo pathlib: {tiempo_pathlib:.6f}s")

Además, pathlib implementa un sistema de caché interno para algunas operaciones, lo que puede mejorar el rendimiento en escenarios donde se accede repetidamente a la misma información de un archivo.

Integración con otras bibliotecas

Desde Python 3.6, muchas funciones de la biblioteca estándar que antes solo aceptaban cadenas de texto ahora también aceptan objetos Path:

# Abrir un archivo
with open(Path('archivo.txt'), 'r') as f:
    contenido = f.read()

# Usar con otras funciones
import json
datos = json.loads(Path('datos.json').read_text())

Esta integración elimina la necesidad de convertir constantemente entre objetos Path y cadenas.

Manejo de rutas especiales

Pathlib proporciona métodos convenientes para acceder a rutas especiales del sistema:

# Con os.path
import os
home = os.path.expanduser('~')
actual = os.getcwd()

# Con pathlib
from pathlib import Path
home = Path.home()
actual = Path.cwd()

Estos métodos hacen que el código sea más claro y expresivo.

Conclusión práctica

Para ilustrar las ventajas de pathlib en un caso de uso real, comparemos cómo se implementaría una función para buscar y procesar archivos:

# Implementación con os.path
import os
import glob

def procesar_logs_os(directorio):
    """Procesa archivos de log en el directorio especificado."""
    if not os.path.exists(directorio) or not os.path.isdir(directorio):
        raise ValueError(f"El directorio {directorio} no existe")
    
    archivos_log = glob.glob(os.path.join(directorio, '*.log'))
    resultados = []
    
    for archivo in archivos_log:
        if os.path.getsize(archivo) > 0:
            with open(archivo, 'r') as f:
                primera_linea = f.readline().strip()
            nombre_base = os.path.basename(archivo)
            resultados.append((nombre_base, primera_linea))
    
    return resultados

# Implementación con pathlib
from pathlib import Path

def procesar_logs_pathlib(directorio):
    """Procesa archivos de log en el directorio especificado."""
    dir_path = Path(directorio)
    if not dir_path.exists() or not dir_path.is_dir():
        raise ValueError(f"El directorio {directorio} no existe")
    
    resultados = []
    
    for archivo in dir_path.glob('*.log'):
        if archivo.stat().st_size > 0:
            primera_linea = archivo.read_text().splitlines()[0]
            resultados.append((archivo.name, primera_linea))
    
    return resultados

La versión con pathlib es más concisa, más legible y menos propensa a errores, lo que demuestra por qué se ha convertido en la opción recomendada para el manejo de rutas en Python moderno.

Manipulación de archivos y directorios

El módulo pathlib no solo simplifica la representación de rutas, sino que también proporciona una interfaz completa para manipular archivos y directorios de forma intuitiva. Esta funcionalidad nos permite realizar operaciones comunes del sistema de archivos sin necesidad de recurrir a módulos adicionales en muchos casos.

Lectura y escritura de archivos

Pathlib ofrece métodos directos para las operaciones más comunes de lectura y escritura:

from pathlib import Path

# Leer el contenido completo de un archivo como texto
ruta_config = Path('configuracion.ini')
contenido = ruta_config.read_text(encoding='utf-8')
print(f"Configuración cargada: {len(contenido)} caracteres")

# Leer el contenido como líneas individuales
lineas = contenido.splitlines()
for i, linea in enumerate(lineas[:5], 1):
    print(f"Línea {i}: {linea}")

# Escribir texto a un archivo (crea o sobrescribe)
ruta_salida = Path('salida.log')
ruta_salida.write_text('Registro iniciado\nOperación completada\n', encoding='utf-8')

Para archivos binarios, pathlib proporciona métodos específicos:

# Leer datos binarios
ruta_imagen = Path('imagen.png')
datos_binarios = ruta_imagen.read_bytes()
print(f"Tamaño de la imagen: {len(datos_binarios)} bytes")

# Escribir datos binarios
nueva_imagen = Path('copia_imagen.png')
nueva_imagen.write_bytes(datos_binarios)

Para operaciones más complejas o procesamiento línea por línea, podemos combinar objetos Path con la función open():

# Procesar un archivo línea por línea de forma eficiente
archivo_grande = Path('datos_extensos.csv')
total_lineas = 0

with open(archivo_grande, 'r', encoding='utf-8') as f:
    for linea in f:
        total_lineas += 1
        # Procesar cada línea sin cargar todo el archivo en memoria
        
print(f"Total de líneas procesadas: {total_lineas}")

Creación y gestión de archivos

Pathlib facilita la creación y manipulación de archivos:

# Crear un archivo vacío o actualizar su timestamp
archivo_log = Path('app.log')
archivo_log.touch(exist_ok=True)  # No falla si ya existe

# Verificar si podemos escribir en el archivo
if os.access(archivo_log, os.W_OK):
    archivo_log.write_text('Nueva entrada de log\n', encoding='utf-8')

# Renombrar un archivo
if archivo_log.exists():
    nuevo_nombre = archivo_log.with_name('app_old.log')
    archivo_log.rename(nuevo_nombre)
    print(f"Archivo renombrado a: {nuevo_nombre}")
    
# Eliminar un archivo
archivo_temporal = Path('temp.dat')
if archivo_temporal.exists():
    archivo_temporal.unlink()
    print(f"Archivo {archivo_temporal} eliminado")

Para operaciones condicionales, podemos usar un bloque try-except:

# Eliminar un archivo de forma segura
archivo_cache = Path('cache.tmp')
try:
    archivo_cache.unlink(missing_ok=True)  # Python 3.8+
    print("Cache eliminado o no existía")
except FileNotFoundError:
    # En versiones anteriores a Python 3.8
    print("El archivo no existía")

Gestión de directorios

La creación y manipulación de directorios es igualmente sencilla con pathlib:

# Crear un directorio
directorio_datos = Path('datos')
directorio_datos.mkdir(exist_ok=True)  # No falla si ya existe

# Crear estructura de directorios anidados
ruta_proyecto = Path('proyecto/src/modulos')
ruta_proyecto.mkdir(parents=True, exist_ok=True)

# Eliminar un directorio (debe estar vacío)
directorio_temp = Path('temp')
if directorio_temp.exists():
    directorio_temp.rmdir()
    print(f"Directorio {directorio_temp} eliminado")

Para operaciones más complejas con directorios, como eliminar directorios no vacíos, necesitamos combinar pathlib con el módulo shutil:

import shutil

# Eliminar un directorio y todo su contenido
directorio_obsoleto = Path('old_data')
if directorio_obsoleto.exists():
    shutil.rmtree(directorio_obsoleto)
    print(f"Directorio {directorio_obsoleto} y su contenido eliminados")

Copiado de archivos y directorios

Aunque pathlib no incluye métodos directos para copiar archivos, podemos combinarlo fácilmente con shutil:

import shutil

# Copiar un archivo preservando metadatos
archivo_original = Path('documento.pdf')
archivo_copia = Path('backup/documento.pdf')

# Asegurar que el directorio destino existe
archivo_copia.parent.mkdir(parents=True, exist_ok=True)

if archivo_original.exists():
    shutil.copy2(archivo_original, archivo_copia)
    print(f"Archivo copiado a {archivo_copia}")
    
# Copiar un directorio completo
dir_origen = Path('proyecto')
dir_destino = Path('backup/proyecto')
if dir_origen.exists() and dir_origen.is_dir():
    shutil.copytree(dir_origen, dir_destino, dirs_exist_ok=True)  # dirs_exist_ok es para Python 3.8+
    print(f"Directorio copiado a {dir_destino}")

Pathlib facilita la navegación y exploración del sistema de archivos:

# Listar todos los elementos en un directorio
directorio = Path('proyecto')
print("Contenido del directorio:")
for item in directorio.iterdir():
    tipo = "Directorio" if item.is_dir() else "Archivo"
    print(f"- {item.name} ({tipo})")

# Filtrar archivos por extensión
print("\nArchivos Python:")
for archivo_py in directorio.glob('*.py'):
    print(f"- {archivo_py.name}")

# Búsqueda recursiva
print("\nTodos los archivos de configuración:")
for config in directorio.rglob('*.json'):
    print(f"- {config.relative_to(directorio)}")

Para casos más específicos, podemos combinar los métodos de búsqueda con comprensiones de lista:

# Encontrar archivos modificados en las últimas 24 horas
import time
from datetime import datetime, timedelta

ayer = datetime.now() - timedelta(days=1)
timestamp_ayer = ayer.timestamp()

archivos_recientes = [
    archivo for archivo in Path('.').rglob('*.*')
    if archivo.is_file() and archivo.stat().st_mtime > timestamp_ayer
]

print(f"\nArchivos modificados en las últimas 24 horas: {len(archivos_recientes)}")
for archivo in archivos_recientes[:5]:  # Mostrar solo los primeros 5
    mod_time = datetime.fromtimestamp(archivo.stat().st_mtime)
    print(f"- {archivo.name} (Modificado: {mod_time.strftime('%Y-%m-%d %H:%M:%S')})")

Gestión de permisos y propietarios

Para manipular permisos de archivos, podemos combinar pathlib con funciones del módulo os:

import os
import stat

# Obtener permisos actuales
archivo = Path('script.py')
if archivo.exists():
    permisos = archivo.stat().st_mode
    print(f"Permisos actuales: {oct(permisos)}")
    
    # Hacer un archivo ejecutable
    os.chmod(archivo, permisos | stat.S_IXUSR | stat.S_IXGRP)
    print("Archivo marcado como ejecutable")
    
    # Verificar si ahora es ejecutable
    if os.access(archivo, os.X_OK):
        print("Confirmado: el archivo es ejecutable")

Monitoreo de archivos y directorios

Podemos usar pathlib para implementar funciones de monitoreo básicas:

import time
from datetime import datetime

# Función para monitorear cambios en un directorio
def monitorear_directorio(ruta, intervalo=5):
    directorio = Path(ruta)
    if not directorio.exists() or not directorio.is_dir():
        raise ValueError(f"{ruta} no es un directorio válido")
    
    # Capturar estado inicial
    archivos_previos = {archivo: archivo.stat().st_mtime 
                        for archivo in directorio.glob('*.*') if archivo.is_file()}
    
    print(f"Monitoreando {directorio}. Presiona Ctrl+C para detener.")
    try:
        while True:
            time.sleep(intervalo)
            
            # Capturar estado actual
            archivos_actuales = {archivo: archivo.stat().st_mtime 
                                for archivo in directorio.glob('*.*') if archivo.is_file()}
            
            # Detectar archivos nuevos
            nuevos = set(archivos_actuales.keys()) - set(archivos_previos.keys())
            for archivo in nuevos:
                print(f"[{datetime.now().strftime('%H:%M:%S')}] NUEVO: {archivo.name}")
            
            # Detectar archivos modificados
            for archivo in set(archivos_actuales.keys()) & set(archivos_previos.keys()):
                if archivos_actuales[archivo] > archivos_previos[archivo]:
                    print(f"[{datetime.now().strftime('%H:%M:%S')}] MODIFICADO: {archivo.name}")
            
            # Detectar archivos eliminados
            eliminados = set(archivos_previos.keys()) - set(archivos_actuales.keys())
            for archivo in eliminados:
                print(f"[{datetime.now().strftime('%H:%M:%S')}] ELIMINADO: {archivo.name}")
            
            # Actualizar estado previo
            archivos_previos = archivos_actuales
            
    except KeyboardInterrupt:
        print("\nMonitoreo finalizado.")

# Uso:
# monitorear_directorio('proyecto/src')

Casos de uso prácticos

Veamos algunos ejemplos prácticos que combinan varias operaciones:

1. Organizar archivos por tipo de extensión:

def organizar_por_extension(directorio):
    """Organiza archivos en subdirectorios según su extensión."""
    dir_base = Path(directorio)
    if not dir_base.exists() or not dir_base.is_dir():
        raise ValueError(f"{directorio} no es un directorio válido")
    
    # Encontrar todos los archivos (no directorios)
    archivos = [f for f in dir_base.iterdir() if f.is_file()]
    
    # Crear directorios para cada extensión y mover archivos
    for archivo in archivos:
        # Obtener extensión (sin el punto) o 'sin_extension'
        extension = archivo.suffix[1:] if archivo.suffix else 'sin_extension'
        
        # Crear directorio para esta extensión
        dir_extension = dir_base / extension
        dir_extension.mkdir(exist_ok=True)
        
        # Definir ruta destino
        destino = dir_extension / archivo.name
        
        # Mover archivo
        try:
            archivo.rename(destino)
            print(f"Movido: {archivo.name} → {extension}/{archivo.name}")
        except Exception as e:
            print(f"Error al mover {archivo.name}: {e}")

2. Crear una copia de seguridad de archivos importantes:

def crear_backup(directorio_origen, directorio_destino):
    """Crea una copia de seguridad de archivos importantes."""
    origen = Path(directorio_origen)
    destino = Path(directorio_destino)
    
    # Verificar directorios
    if not origen.exists() or not origen.is_dir():
        raise ValueError(f"Directorio origen no válido: {origen}")
    
    # Crear directorio destino si no existe
    destino.mkdir(parents=True, exist_ok=True)
    
    # Extensiones de archivos importantes
    extensiones_importantes = {'.py', '.java', '.js', '.html', '.css', '.md', '.txt', '.pdf', '.docx'}
    
    # Encontrar archivos importantes
    archivos_importantes = [
        archivo for archivo in origen.rglob('*')
        if archivo.is_file() and archivo.suffix.lower() in extensiones_importantes
    ]
    
    # Copiar archivos manteniendo la estructura de directorios
    for archivo in archivos_importantes:
        # Calcular ruta relativa
        ruta_relativa = archivo.relative_to(origen)
        ruta_destino = destino / ruta_relativa
        
        # Crear directorios padres en destino
        ruta_destino.parent.mkdir(parents=True, exist_ok=True)
        
        # Copiar archivo
        import shutil
        shutil.copy2(archivo, ruta_destino)
        print(f"Respaldado: {ruta_relativa}")
    
    print(f"Backup completado: {len(archivos_importantes)} archivos copiados")

3. Limpiar archivos temporales:

def limpiar_temporales(directorio, dias=7):
    """Elimina archivos temporales más antiguos que el número de días especificado."""
    dir_base = Path(directorio)
    if not dir_base.exists() or not dir_base.is_dir():
        raise ValueError(f"{directorio} no es un directorio válido")
    
    # Calcular timestamp límite
    import time
    from datetime import datetime, timedelta
    
    fecha_limite = datetime.now() - timedelta(days=dias)
    timestamp_limite = fecha_limite.timestamp()
    
    # Extensiones típicas de archivos temporales
    extensiones_temp = {'.tmp', '.temp', '.bak', '.log', '.cache'}
    
    # Encontrar y eliminar archivos antiguos
    total_eliminados = 0
    total_bytes_liberados = 0
    
    for archivo in dir_base.rglob('*'):
        if (archivo.is_file() and 
            (archivo.suffix.lower() in extensiones_temp or archivo.name.startswith('~')) and
            archivo.stat().st_mtime < timestamp_limite):
            
            # Guardar tamaño antes de eliminar
            tamano = archivo.stat().st_size
            
            # Eliminar archivo
            try:
                archivo.unlink()
                total_eliminados += 1
                total_bytes_liberados += tamano
                print(f"Eliminado: {archivo}")
            except Exception as e:
                print(f"Error al eliminar {archivo}: {e}")
    
    # Mostrar resumen
    mb_liberados = total_bytes_liberados / (1024 * 1024)
    print(f"\nLimpieza completada: {total_eliminados} archivos eliminados ({mb_liberados:.2f} MB liberados)")

Estos ejemplos demuestran cómo pathlib simplifica considerablemente la manipulación de archivos y directorios en Python, permitiéndonos escribir código más limpio, legible y mantenible para operaciones comunes del sistema de archivos.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Python online

Ejercicios de esta lección Módulo pathlib

Evalúa tus conocimientos de esta lección Módulo pathlib con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la representación de rutas como objetos Path y sus ventajas sobre cadenas de texto.
  • Crear, modificar y resolver rutas utilizando pathlib de manera portable entre sistemas operativos.
  • Realizar operaciones comunes con archivos y directorios: lectura, escritura, creación, eliminación y navegación.
  • Utilizar métodos de búsqueda y filtrado de archivos con glob y rglob.
  • Comparar pathlib con os.path y entender por qué pathlib es la opción recomendada en Python moderno.