Saltar a contenido

TRBAJO CON RUTAS: pathlib y os.path

Python ofrece dos módulos para trabajar con rutas de ficheros y directorios:

  • pathlib (Python 3.4+): orientado a objetos, moderno y recomendado
  • os.path: módulo clásico, basado en cadenas de texto

Se verá con más detalle pathlib por ser la opción actualmente recomendada.

El módulo pathlib

pathlib representa las rutas como objetos de la clase Path. Esto permite encadenar operaciones de forma intuitiva y escribir código portable entre sistemas operativos sin preocuparse por los separadores de ruta.

1
2
3
4
5
from pathlib import Path

ruta = Path("datos") / "alumnos" / "notas.txt"
print(ruta)       # datos/alumnos/notas.txt (Linux/macOS)
                  # datos\alumnos\notas.txt (Windows)

El operador / entre objetos Path construye rutas de forma portable. En Windows de forma automática es cambiado por \

Ruta relativa al propio script

Un patrón muy habitual es construir rutas relativas al fichero .py que se está ejecutando, independientemente del directorio desde el que se lance el programa:

1
2
3
4
5
from pathlib import Path

# __file__ es la ruta al fichero .py actual
BASE = Path(__file__).parent
ruta_datos = BASE / "datos" / "notas.txt"
- Path(__file__) es un objeto que apunta a la ruta del fichero que se está ejecutando, por ejemplo /ruta/proyecto/app/main.py. Al hacer Path(__file__).parent obtenemos un objeto de tipo PATH que apuntará a la carpeta del ejecutable /ruta/proyecto/app. - BASE se define como constante porque su valor se calcula una vez y no debería cambiar durante la ejecución

Path(".") vs Path(__file__).parent

Ambas formas son válidas, pero sirven para propósitos distintos.

Vamos a verlo con un ejemplo, supongamos que tenemos un script en C:\Users\Ana\proyecto\scripts\demo.py y lo ejecutas desde C:\Users\Ana:

Path(".") Path(__file__).parent
Apunta a C:\Users\Ana (directorio actual) C:\Users\Ana\proyecto\scripts (carpeta del script)
Cambia según Desde dónde se lanza el programa Nunca — siempre es la carpeta del .py
Úsar para Ficheros del usuario en su directorio de trabajo Recursos del propio programa (datos, config, plantillas)
# Para listar ficheros donde el usuario está trabajando
for f in Path(".").glob("*.csv"):
    print(f.name)

# Para cargar datos que vienen con el proyecto
BASE = Path(__file__).parent
config = BASE / "config" / "ajustes.json"

Operaciones básicas con pathlib

Comprobar existencia

1
2
3
4
5
6
7
from pathlib import Path

ruta = Path("datos/notas.txt")

ruta.exists()    # True si existe (fichero o directorio)
ruta.is_file()   # True solo si es un fichero
ruta.is_dir()    # True solo si es un directorio

Obtener partes de la ruta

1
2
3
4
5
6
7
8
from pathlib import Path

ruta = Path("datos/alumnos/notas.txt")

ruta.name        # 'notas.txt'          — nombre con extensión
ruta.stem        # 'notas'              — nombre sin extensión
ruta.suffix      # '.txt'              — extensión
ruta.parent      # Path('datos/alumnos') — directorio padre

Crear directorios

1
2
3
4
5
from pathlib import Path

Path("datos/exportaciones").mkdir(parents=True, exist_ok=True)
# parents=True: crea los directorios intermedios si no existen
# exist_ok=True: no lanza error si el directorio ya existe

Listar ficheros de un directorio

1
2
3
4
5
from pathlib import Path

carpeta = Path("datos")
for fichero in carpeta.iterdir():
    print(fichero.name)

Para filtrar aplicando patrones comodín, se usa glob(). El método glob devuelve un iterador (objeto que te da elementos uno a uno) con los archivos que cumplen con el patrón.

Por ejemplo si queremos filtrar por extensión:

1
2
3
4
5
6
from pathlib import Path

carpeta = Path("datos")
ficheros_txt = list(carpeta.glob("*.txt"))
for f in ficheros_txt:
    print(f.name)
  • Convertimos en lista el iterador para poder recorrerlo con for/in

De forma resumida. Patrones que podemos aplicar:

base = Path("proyecto")
base.glob("*.txt")           # *  -> cualquier secuencia: todos los .txt en esa carpeta
base.glob("foto?.jpg")       # ?  -> un carácter: foto1.jpg, fotoA.jpg
base.glob("archivo[12].csv") # [abc] -> un carácter del conjunto: archivo1.csv, archivo2.csv
base.glob("tema[1-5].md")    # [a-z] -> un carácter en rango: tema1.md ... tema5.md
base.glob("[!._]*")          # [!abc] -> un carácter fuera del conjunto: no empieza por . ni _
base.glob("**/*.py")         # ** -> búsqueda recursiva: todos los .py en carpeta actual y subcarpetas
base.rglob("*.py")           # recursiva también (equivalente práctico a **/*.py)

glob viene de global (en Unix), y se refiere a la expansión de patrones con comodines para encontrar archivos/rutas.

Leer y escribir directamente en ficheros con pathlib

Path tiene métodos para leer y escribir sin necesidad de open():

1
2
3
4
5
6
7
8
9
from pathlib import Path

ruta = Path("datos/notas.txt")

# Lectura directa
contenido = ruta.read_text(encoding="utf-8")

# Escritura directa (equivalente a modo "w")
ruta.write_text("Ana García: 8.5\n", encoding="utf-8")

Investiga: Por qué read_text() y write_text() son convenientes para operaciones simples como lecturas línea a línea o escrituras incrementales y deberíamos seguir usando open() con with.

El módulo os.path

El módulo clásico os.path trabaja con rutas como cadenas de texto. Aunque pathlib es preferible en código nuevo, es importante conocerlo porque aparece en mucho código existente (legacy).

import os.path

ruta = "datos/alumnos/notas.txt"

os.path.exists(ruta)          # True si existe
os.path.isfile(ruta)          # True si es fichero
os.path.isdir(ruta)           # True si es directorio
os.path.basename(ruta)        # 'notas.txt'
os.path.dirname(ruta)         # 'datos/alumnos'
os.path.splitext(ruta)        # ('datos/alumnos/notas', '.txt')

Para construir rutas portables con os.path:

1
2
3
4
5
import os.path

ruta = os.path.join("datos", "alumnos", "notas.txt")
# En Linux/macOS: 'datos/alumnos/notas.txt'
# En Windows:    'datos\alumnos\notas.txt'

Comparativa pathlib vs os.path

Operación pathlib os.path
Construir ruta Path("a") / "b" os.path.join("a", "b")
¿Existe? ruta.exists() os.path.exists(ruta)
¿Es fichero? ruta.is_file() os.path.isfile(ruta)
Nombre del fichero ruta.name os.path.basename(ruta)
Directorio padre ruta.parent os.path.dirname(ruta)
Extensión ruta.suffix os.path.splitext(ruta)[1]
Leer texto ruta.read_text()

mala práctica: concatenar rutas con strings

# Incorrecto: no es portable, falla en Windows
ruta = "datos/" + "alumnos/" + "notas.txt"
# Correcto con pathlib
from pathlib import Path
ruta = Path("datos") / "alumnos" / "notas.txt"
# Correcto con os.path (si se usa la librería clásica)
import os.path
ruta = os.path.join("datos", "alumnos", "notas.txt")

Usa pathlib en código nuevo

A partir de Python 3.6, open() y la mayoría de funciones que aceptan rutas también aceptan objetos Path directamente. No hay ninguna razón para usar concatenación de strings en código moderno.