Manipulación de Rutas de Archivos y Directorios en Python

PythonBeginner
Practicar Ahora

Introducción

En este laboratorio, aprenderá a manejar rutas de archivos y directorios en Python utilizando el módulo moderno pathlib. Aunque el módulo más antiguo os.path todavía es común, pathlib ofrece un enfoque orientado a objetos que hace que la manipulación de rutas sea más intuitiva y legible. Aprenderá a crear, inspeccionar, validar y manipular rutas, archivos y directorios de una manera que funciona en diferentes sistemas operativos como Windows, macOS y Linux.

Al finalizar este laboratorio, dominará:

  • La unión correcta de componentes de ruta.
  • La creación y gestión de directorios.
  • La diferenciación entre rutas absolutas y relativas.
  • La validación de la existencia y el tipo de rutas.
  • La realización de operaciones comunes de archivos como copiar, mover y eliminar.
  • El recorrido de árboles de directorios para encontrar archivos y carpetas.

Operaciones Básicas de Rutas

En este paso, aprenderá los fundamentos de la manipulación de rutas utilizando el módulo pathlib. Esto incluye unir componentes de ruta, obtener el directorio de trabajo actual y crear nuevos directorios.

El módulo pathlib proporciona una forma elegante y multiplataforma de manejar rutas del sistema de archivos. En Windows, las rutas utilizan barras invertidas (\), mientras que en sistemas tipo Unix (Linux, macOS), utilizan barras diagonales (/). Usar pathlib asegura que su código funcione en cualquier plataforma sin ajustes manuales.

Nota: Si no está familiarizado con el uso de WebIDE (similar a VS Code), por favor, aprenda primero en este laboratorio.

Primero, veamos cómo unir componentes de ruta. El operador / es un atajo intuitivo para joinpath().

  1. Abra el archivo main.py desde el explorador de archivos de la izquierda.
  2. Reemplace el contenido de main.py con el siguiente código para unir componentes de ruta e imprimir el resultado.
from pathlib import Path

## Unir componentes de ruta usando el operador /
my_path = Path('usr') / 'bin' / 'spam'
print(my_path)
  1. Ejecute el script desde la terminal:
python3 main.py

Verá la siguiente salida, que demuestra cómo pathlib forma correctamente una ruta estilo Unix:

usr/bin/spam

A continuación, obtengamos el directorio de trabajo actual usando Path.cwd().

  1. Reemplace el contenido de main.py con el siguiente código (esto reemplaza el código anterior):
from pathlib import Path

## Obtener el directorio de trabajo actual
cwd = Path.cwd()
print(cwd)
  1. Ejecute el script nuevamente:
python3 main.py

La salida mostrará el directorio de trabajo actual, que es /home/labex/project en este entorno:

/home/labex/project

Finalmente, creemos un nuevo directorio. Ocurre un FileNotFoundError si intenta crear un directorio donde el directorio padre no existe. Para evitar esto, use mkdir(parents=True), que crea todos los directorios padres necesarios.

  1. Reemplace el contenido de main.py con el siguiente código para crear una estructura de directorios anidada:
from pathlib import Path

## Obtener el directorio de trabajo actual
cwd = Path.cwd()

## Definir la ruta para los nuevos directorios anidados
new_dirs = cwd / 'delicious' / 'walnut' / 'waffles'

## Crear los directorios, incluyendo cualquier padre faltante
new_dirs.mkdir(parents=True, exist_ok=True)

print(f"Successfully created: {new_dirs}")

El argumento exist_ok=True previene un error si el directorio ya existe. Esto significa que puede ejecutar el script varias veces sin errores.

  1. Ejecute el script:
python3 main.py

El script creará los directorios anidados e imprimirá un mensaje de confirmación:

Successfully created: /home/labex/project/delicious/walnut/waffles

Puede verificar la creación del directorio ejecutando ls -R delicious en la terminal.

Rutas Absolutas vs. Relativas

En este paso, aprenderá a distinguir y trabajar con rutas absolutas y relativas.

  • Una ruta absoluta comienza desde la raíz del sistema de archivos (ej. / en Linux o C:\ en Windows).
  • Una ruta relativa se define desde el directorio de trabajo actual.

pathlib proporciona métodos para verificar si una ruta es absoluta, resolver una ruta a su forma absoluta y encontrar una ruta relativa desde un punto de partida.

Primero, veamos cómo verificar si una ruta es absoluta usando is_absolute().

  1. Reemplace el contenido de main.py con el siguiente código:
from pathlib import Path

## Verificar si las rutas son absolutas
print(f"Path('/') is absolute: {Path('/').is_absolute()}")
print(f"Path('..') is absolute: {Path('..').is_absolute()}")
  1. Ejecute el script:
python3 main.py

La salida confirma que / es una ruta absoluta, mientras que .. (directorio padre) es relativa.

Path('/') is absolute: True
Path('..') is absolute: False

A continuación, puede obtener la ruta absoluta completa de una ruta relativa usando resolve(). Esto es útil para convertir rutas relativas como . (directorio actual) o .. (directorio padre) a su forma completa e inequívoca.

  1. Reemplace el contenido de main.py con el siguiente código:
from pathlib import Path

## Resolver una ruta relativa a su forma absoluta
## '..' se refiere al directorio padre del directorio de trabajo actual
resolved_path = Path('..').resolve()

print(f"Current working directory: {Path.cwd()}")
print(f"Resolved path of '..': {resolved_path}")
  1. Ejecute el script:
python3 main.py

La salida mostrará el directorio actual y la ruta absoluta resuelta de su padre.

Current working directory: /home/labex/project
Resolved path of '..': /home/labex

Finalmente, puede calcular una ruta relativa de una ruta a otra usando relative_to().

  1. Reemplace el contenido de main.py con el siguiente código para calcular una ruta relativa:
from pathlib import Path

## Obtener una ruta relativa desde un punto de partida
## Esto muestra la ruta a 'passwd' relativa al directorio raíz '/'
relative_path = Path('/etc/passwd').relative_to('/')
print(f"Path to '/etc/passwd' relative to '/': {relative_path}")
  1. Ejecute el script:
python3 main.py

La salida muestra la ruta relativa al directorio raíz:

Path to '/etc/passwd' relative to '/': etc/passwd

Validación e Inspección de Rutas

En este paso, aprenderá a verificar las propiedades de una ruta, como si existe y si es un archivo o un directorio. También aprenderá a recuperar metadatos de archivos como su tamaño.

Una tarea común es verificar si un archivo o directorio existe antes de intentar acceder a él. El método exists() es perfecto para esto.

  1. Abra main.py y reemplace su contenido con el siguiente código. Esto verificará la existencia del directorio actual (.), un archivo (setup.py) y un archivo inexistente.
from pathlib import Path

## Verificar si las rutas existen
print(f"Path('.') exists: {Path('.').exists()}")
print(f"Path('setup.py') exists: {Path('setup.py').exists()}")
print(f"Path('nonexistentfile') exists: {Path('nonexistentfile').exists()}")
  1. Ejecute el script:
python3 main.py

La salida confirmará qué rutas existen:

Path('.') exists: True
Path('setup.py') exists: True
Path('nonexistentfile') exists: False

También puede ser más específico y verificar si una ruta apunta a un archivo con is_file() o a un directorio con is_dir().

  1. Reemplace el contenido de main.py con el siguiente código para realizar estas comprobaciones:
from pathlib import Path

## Verificar si una ruta es un archivo
print(f"Path('setup.py').is_file(): {Path('setup.py').is_file()}")
print(f"Path('/home').is_file(): {Path('/home').is_file()}")

## Verificar si una ruta es un directorio
print(f"Path('/').is_dir(): {Path('/').is_dir()}")
print(f"Path('setup.py').is_dir(): {Path('setup.py').is_dir()}")
  1. Ejecute el script:
python3 main.py

La salida identificará correctamente el tipo de cada ruta:

Path('setup.py').is_file(): True
Path('/home').is_file(): False
Path('/').is_dir(): True
Path('setup.py').is_dir(): False

Finalmente, para obtener información detallada del archivo, como su tamaño, puede usar el método stat(). Devuelve un objeto con varios atributos, incluido st_size para el tamaño del archivo en bytes.

  1. Reemplace el contenido de main.py con el siguiente código para obtener el tamaño de un archivo del sistema:
from pathlib import Path
import os

## Definir la ruta a un archivo
file_path = Path('/bin/python3')

## Obtener estadísticas del archivo
stat_info = file_path.stat()

## Imprimir el tamaño en bytes
print(f"File: {file_path}")
print(f"Size: {stat_info.st_size} bytes")
  1. Ejecute el script:
python3 main.py

La salida mostrará el tamaño del ejecutable de Python en bytes. El tamaño exacto puede variar.

File: /bin/python3
Size: 10024 bytes

Gestión de Archivos y Carpetas

En este paso, aprenderá a realizar operaciones comunes del sistema de archivos como copiar, mover, renombrar y eliminar archivos y carpetas utilizando el módulo shutil en combinación con pathlib.

El módulo shutil proporciona funciones de alto nivel para operaciones de archivos. Comencemos con la copia de archivos. shutil.copy() copia un único archivo a una nueva ubicación.

  1. Abra el archivo manage_files.py desde el explorador de archivos de la izquierda.
  2. Reemplace el contenido de manage_files.py con el siguiente código. Esto copiará spam.txt al directorio /tmp/delicious.
import shutil
from pathlib import Path

## Definir origen y destino
source_file = Path('/tmp/spam.txt')
destination_dir = Path('/tmp/delicious')

## Copiar el archivo
shutil.copy(source_file, destination_dir)

print(f"Copied '{source_file}' to '{destination_dir}'")

## También puede renombrar el archivo mientras lo copia
shutil.copy('/tmp/eggs.txt', '/tmp/delicious/eggs2.txt')
print("Copied and renamed 'eggs.txt' to 'eggs2.txt'")
  1. Ejecute el script:
python3 manage_files.py

Para copiar un árbol de directorios completo, use shutil.copytree().

  1. Agregue las siguientes líneas al final de manage_files.py (añádalas después del código existente):
## Copiar un árbol de directorios completo
shutil.copytree('/tmp/bacon', '/tmp/bacon_backup')
print("Copied directory '/tmp/bacon' to '/tmp/bacon_backup'")

Nota: Si ejecuta este script varias veces, shutil.copytree() fallará porque el directorio de destino ya existe. Este es el comportamiento esperado. Por ahora, ejecútelo solo una vez.

  1. Ejecute el script nuevamente para verlo en acción.

A continuación, veamos cómo mover y renombrar archivos con shutil.move(). Esta función puede mover un archivo a un directorio diferente o renombrarlo.

  1. Reemplace el contenido completo de manage_files.py con el siguiente código para mover bacon.txt:
import shutil
from pathlib import Path

## Crear el directorio de destino si no existe
Path('/tmp/eggs').mkdir(parents=True, exist_ok=True)

## Mover un archivo a un directorio (solo si el origen existe)
bacon_source = Path('/tmp/bacon.txt')
if bacon_source.exists():
    shutil.move('/tmp/bacon.txt', '/tmp/eggs')
    print("Moved '/tmp/bacon.txt' to '/tmp/eggs'")
else:
    print("'/tmp/bacon.txt' not found (may have been moved already)")

## Mover y renombrar un archivo
## Primero, recreemos el archivo para moverlo
Path('/tmp/bacon.txt').touch()
shutil.move('/tmp/bacon.txt', '/tmp/eggs/new_bacon.txt')
print("Moved and renamed '/tmp/bacon.txt' to '/tmp/eggs/new_bacon.txt'")

Nota: El código verifica si el archivo de origen existe antes de moverlo, por lo que puede ejecutar este script varias veces de forma segura. La segunda parte siempre creará un nuevo archivo y lo moverá.

  1. Ejecute el script.

Finalmente, veamos la eliminación de archivos y carpetas.

  • Path.unlink() elimina un único archivo.
  • Path.rmdir() elimina un directorio vacío.
  • shutil.rmtree() elimina un directorio y todo su contenido.
  1. Reemplace el contenido completo de manage_files.py con lo siguiente:
from pathlib import Path
import shutil

## Crear un archivo para eliminar
file_to_delete = Path('/tmp/deleteme.txt')
file_to_delete.touch()
print(f"Created '{file_to_delete}'")

## Eliminar el archivo
file_to_delete.unlink()
print(f"Deleted '{file_to_delete}'")

## Crear un directorio para eliminar
dir_to_delete = Path('/tmp/temp_dir')
dir_to_delete.mkdir(exist_ok=True)
## rmdir() solo funciona en directorios vacíos
dir_to_delete.rmdir()
print(f"Deleted empty directory '{dir_to_delete}'")

## Usar shutil.rmtree() para eliminar un directorio y su contenido
## Nota: Esto fallará si el directorio no existe, lo cual está bien para este ejemplo
if Path('/tmp/bacon_backup').exists():
    shutil.rmtree('/tmp/bacon_backup')
    print("Deleted directory '/tmp/bacon_backup' and all its contents")
else:
    print("Directory '/tmp/bacon_backup' does not exist (may have been deleted already)")

Nota: Si ejecuta este script varias veces, algunas operaciones pueden fallar porque los archivos o directorios ya han sido eliminados. Esto es normal y demuestra que estas operaciones no son idempotentes (no se pueden repetir de forma segura).

  1. Ejecute el script para realizar las operaciones de eliminación.

Recorrido de Directorios

En este paso, aprenderás cómo listar el contenido de un directorio y recorrer recursivamente un árbol de directorios.

Para listar el contenido de un directorio, puedes usar el método iterdir(), que devuelve un iterador de todos los elementos (archivos y directorios) en la ruta.

  1. Primero, configuremos una estructura de directorio de ejemplo. Ejecuta este comando en la terminal:
mkdir -p ~/project/data/level1 && touch ~/project/data/file0.txt ~/project/data/level1/file1.txt
  1. Ahora, abre main.py y reemplaza su contenido con el siguiente código para listar el contenido del directorio /usr/bin.
from pathlib import Path

## Listar contenido de un directorio
print("Contenido de '/usr/bin':")
count = 0
for f in Path('/usr/bin').iterdir():
    if count < 5: ## Imprimir los primeros 5 elementos para mantener la salida corta
        print(f)
        count += 1
    else:
        break
  1. Ejecuta el script:
python3 main.py

La salida mostrará los primeros archivos y directorios dentro de /usr/bin. La lista exacta puede variar.

Contenido de '/usr/bin':
/usr/bin/[
/usr/bin/addpart
/usr/bin/apt
/usr/bin/apt-cache
/usr/bin/apt-cdrom

Puedes combinar iterdir() con stat() para calcular el tamaño total de todos los archivos en un directorio.

  1. Reemplaza el contenido de main.py con el siguiente código. Ten en cuenta que este código solo suma los tamaños de los hijos directos y no recursa en subdirectorios.
from pathlib import Path

total_size = 0
directory = Path('/usr/bin')

for sub_path in directory.iterdir():
    if sub_path.is_file(): ## Asegurar que solo sumamos el tamaño de los archivos
        total_size += sub_path.stat().st_size

print(f"Tamaño total de archivos en '{directory}': {total_size} bytes")
  1. Ejecuta el script. Imprimirá el tamaño total de todos los archivos directamente dentro de /usr/bin.

Para tareas más complejas, es posible que necesites "recorrer" (walk) un árbol de directorios completo, visitando todos los subdirectorios y archivos. El método rglob() es perfecto para esto. Busca recursivamente todas las rutas que coincidan con un patrón específico. Usar * como patrón coincide con todo.

  1. Primero, crea una estructura de directorio más compleja para este ejemplo. Ejecuta esto en la terminal:
mkdir -p /tmp/delicious/cats /tmp/delicious/walnut/waffles
touch /tmp/delicious/spam.txt /tmp/delicious/cats/catnames.txt /tmp/delicious/walnut/waffles/butter.txt

Nota: Puedes ejecutar estos comandos varias veces de forma segura. El comando mkdir -p no fallará si los directorios ya existen, y touch actualizará las marcas de tiempo de los archivos si ya existen.

  1. Reemplaza el contenido de main.py con el siguiente código para recorrer el directorio /tmp/delicious:
from pathlib import Path

p = Path('/tmp/delicious')
print(f"Recorriendo el árbol de directorios para: {p}")

for i in p.rglob('*'):
    print(i)
  1. Ejecuta el script:
python3 main.py

La salida listará cada archivo y carpeta dentro de /tmp/delicious y sus subdirectorios, demostrando cómo rglob() recorre todo el árbol.

Recorriendo el árbol de directorios para: /tmp/delicious
/tmp/delicious/spam.txt
/tmp/delicious/eggs2.txt
/tmp/delicious/cats
/tmp/delicious/walnut
/tmp/delicious/cats/catnames.txt
/tmp/delicious/walnut/waffles
/tmp/delicious/walnut/waffles/butter.txt

Resumen

¡Felicidades por completar el laboratorio! Ha aprendido a gestionar eficazmente las rutas de archivos y directorios en Python utilizando el módulo moderno pathlib.

A lo largo de este laboratorio, ha practicado:

  • Crear y unir rutas de manera multiplataforma utilizando objetos Path y el operador /.
  • Obtener el directorio de trabajo actual y crear directorios anidados con mkdir(parents=True).
  • Diferenciar entre rutas absolutas y relativas y convertir entre ellas usando is_absolute(), resolve() y relative_to().
  • Validar rutas con exists(), is_file() e is_dir(), e inspeccionar metadatos de archivos con stat().
  • Realizar tareas esenciales de gestión de archivos como copiar, mover y eliminar con el módulo shutil y los métodos de pathlib.
  • Recorrer estructuras de directorios para listar contenidos con iterdir() y encontrar elementos recursivamente con rglob().

Estas habilidades son fundamentales para escribir aplicaciones Python robustas y mantenibles que interactúan con el sistema de archivos.