29. Interactuar con el sistema operativo: sys y os
Los programas de Python no existen de forma aislada: se ejecutan en un sistema operativo que gestiona archivos, directorios, configuraciones del entorno y cómo los programas se inician y se comunican. Los módulos sys y os proporcionan herramientas para interactuar con este entorno, permitiendo que tus programas acepten argumentos de línea de comandos, lean variables de entorno, naveguen por el sistema de archivos y creen o eliminen directorios.
Comprender estos módulos transforma tus scripts de Python de código aislado en programas que pueden integrarse con el sistema en general, responder a la entrada del usuario al iniciarse y gestionar archivos y carpetas de forma programática.
29.1) Argumentos de línea de comandos y uso de sys.argv
Cuando ejecutas un script de Python desde la terminal o el símbolo del sistema, puedes pasarle información adicional; a esto se le llama argumentos de línea de comandos. Por ejemplo:
python greet.py Alice 25Aquí, greet.py es el nombre del script, y Alice y 25 son argumentos pasados al programa. El módulo sys proporciona acceso a estos argumentos a través de sys.argv, una lista que contiene el nombre del script y todos los argumentos como cadenas.
29.1.1) ¿Qué es sys.argv?
sys.argv es una lista donde:
sys.argv[0]es siempre el nombre del script que se está ejecutandosys.argv[1],sys.argv[2], etc., son los argumentos pasados después del nombre del script- Todos los elementos son cadenas, incluso si parecen números
Creemos un script sencillo para ver cómo funciona esto:
# show_args.py
import sys
print("Script name:", sys.argv[0])
print("Number of arguments:", len(sys.argv))
print("All arguments:", sys.argv)Si ejecutas este script con:
python show_args.py hello world 123Output:
Script name: show_args.py
Number of arguments: 4
All arguments: ['show_args.py', 'hello', 'world', '123']Observa que sys.argv contiene 4 elementos: el nombre del script más tres argumentos. El número 123 se almacena como la cadena '123', no como un entero.
29.1.2) Uso de argumentos de línea de comandos en programas
Los argumentos de línea de comandos permiten que los usuarios personalicen el comportamiento del programa sin modificar el código. Aquí tienes un programa de saludo que usa el primer argumento como un nombre:
# greet.py
import sys
if len(sys.argv) < 2:
print("Usage: python greet.py <name>")
sys.exit(1) # Salir con código de error
name = sys.argv[1]
print(f"Hello, {name}!")Al ejecutar esto:
python greet.py AliceOutput:
Hello, Alice!Si olvidas proporcionar un nombre:
python greet.pyOutput:
Usage: python greet.py <name>El programa comprueba si se proporcionaron suficientes argumentos. Si no, imprime un mensaje de uso y sale con sys.exit(1). El número 1 es un código de salida; por convención, 0 significa éxito y los valores distintos de cero indican errores. Esto ayuda a que otros programas o scripts detecten si tu programa se ejecutó correctamente.
29.1.3) Convertir argumentos a otros tipos
Como todos los argumentos llegan como cadenas, a menudo necesitas convertirlos. Aquí tienes un programa que calcula el área de un rectángulo:
# area.py
import sys
if len(sys.argv) < 3:
print("Usage: python area.py <width> <height>")
sys.exit(1)
try:
width = float(sys.argv[1])
height = float(sys.argv[2])
except ValueError:
print("Error: Width and height must be numbers")
sys.exit(1)
area = width * height
print(f"Area: {area}")Al ejecutar esto:
python area.py 5.5 3.2Output:
Area: 17.6Si el usuario proporciona una entrada no válida:
python area.py five threeOutput:
Error: Width and height must be numbersEl bloque try-except (del Capítulo 25) gestiona los errores de conversión con elegancia, proporcionando comentarios útiles en lugar de fallar con un traceback.
29.2) Obtener información del intérprete y del tiempo de ejecución con sys
El módulo sys proporciona información sobre el propio intérprete de Python y el entorno de ejecución. Esto es útil para depuración, registro o para escribir código que se adapte a diferentes versiones de Python o plataformas.
29.2.1) Información de versión de Python
sys.version y sys.version_info te dicen qué versión de Python está ejecutando tu código:
import sys
print("Python version string:", sys.version)
print("Version info:", sys.version_info)
print(f"Major version: {sys.version_info.major}")
print(f"Minor version: {sys.version_info.minor}")Output (example):
Python version string: 3.11.4 (main, Jul 5 2023, 13:45:01) [GCC 11.2.0]
Version info: sys.version_info(major=3, minor=11, micro=4, releaselevel='final', serial=0)
Major version: 3
Minor version: 11sys.version es una cadena legible por humanos, mientras que sys.version_info es una tupla con nombre que puedes comparar programáticamente:
import sys
if sys.version_info < (3, 8):
print("This program requires Python 3.8 or higher")
sys.exit(1)
print("Python version is compatible")Esto garantiza que tu programa solo se ejecute en versiones de Python compatibles.
29.2.2) Información de la plataforma
sys.platform identifica el sistema operativo:
import sys
print("Platform:", sys.platform)
if sys.platform == "win32":
print("Running on Windows")
elif sys.platform == "darwin":
print("Running on macOS")
elif sys.platform.startswith("linux"):
print("Running on Linux")
else:
print("Running on another platform")Output (on Linux):
Platform: linux
Running on LinuxEsto te permite escribir código específico de la plataforma cuando sea necesario, como usar diferentes rutas de archivos o comandos del sistema.
29.2.3) Ruta de búsqueda de módulos
sys.path es una lista de directorios que Python busca cuando importas módulos:
import sys
print("Module search paths:")
for path in sys.path:
print(f" {path}")Output (example):
Module search paths:
/home/user/projects
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/home/user/.local/lib/python3.11/site-packagesLa primera entrada suele ser el directorio que contiene tu script. Python busca en estas rutas en orden cuando usas import. Comprender sys.path ayuda a depurar errores de importación o a añadir directorios de módulos personalizados.
29.2.4) Salir de programas con códigos de salida
Ya hemos visto sys.exit() usado para detener programas. Puedes pasar un código de salida para indicar éxito o fallo:
import sys
def process_data(filename):
try:
with open(filename) as f:
data = f.read()
# Procesar datos...
return True
except FileNotFoundError:
print(f"Error: File '{filename}' not found", file=sys.stderr)
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python script.py <filename>", file=sys.stderr)
sys.exit(1)
success = process_data(sys.argv[1])
sys.exit(0 if success else 1)Los códigos de salida siguen las convenciones de Unix:
0significa éxito- Los valores distintos de cero indican diferentes tipos de errores
- Otros programas pueden comprobar estos códigos para determinar si tu programa tuvo éxito
29.3) Acceder a variables de entorno con os
Las variables de entorno son pares clave-valor establecidos por el sistema operativo o el usuario que los programas pueden leer. Se usan para configuración, para almacenar rutas, claves de API y otros ajustes que no deberían estar codificados de forma rígida.
29.3.1) Leer variables de entorno
El módulo os proporciona os.environ, un objeto similar a un diccionario que contiene todas las variables de entorno:
import os
# Obtener una variable de entorno específica
home = os.environ.get('HOME') # En Unix/Linux/macOS
print(f"Home directory: {home}")
# Devuelve None si la variable no existe
api_key = os.environ.get('MY_API_KEY')
print(f"API key: {api_key}")Output (on Linux):
Home directory: /home/alice
API key: NonePuedes proporcionar un valor predeterminado:
import os
# Obtener variable de entorno con valor predeterminado
log_level = os.environ.get('LOG_LEVEL', 'INFO')
print(f"Log level: {log_level}")Output (if LOG_LEVEL not set):
Log level: INFO29.3.2) Variables de entorno comunes
Diferentes sistemas operativos proporcionan variables de entorno estándar:
import os
# Variables multiplataforma
print("Path:", os.environ.get('PATH'))
# Unix/Linux/macOS
print("User:", os.environ.get('USER'))
print("Home:", os.environ.get('HOME'))
print("Shell:", os.environ.get('SHELL'))
# Windows
print("User", os.environ.get('USERNAME'))
print("User Profile:", os.environ.get('USERPROFILE'))
print("Temp:", os.environ.get('TEMP'))PATH es particularmente importante: enumera directorios donde el sistema busca programas ejecutables. Cuando escribes un comando como python, el sistema busca en estos directorios.
29.3.3) Establecer variables de entorno
Puedes modificar variables de entorno para tu programa y cualquier subproceso que cree:
import os
# Establecer una variable de entorno
os.environ['MY_CONFIG'] = 'production'
# Leerla de nuevo
print(os.environ.get('MY_CONFIG')) # Output: production
# Eliminar una variable de entorno
del os.environ['MY_CONFIG']Importante: Los cambios en os.environ solo afectan al proceso actual de Python y a cualquier programa que lance. No persisten después de que tu programa termine ni afectan a otros programas.
29.4) Trabajar con rutas de archivos y directorios (os.path, os.getcwd)
Gestionar correctamente las rutas de archivos es crucial para programas que trabajan con archivos. Los módulos os y os.path proporcionan herramientas para construir, manipular y consultar rutas de manera independiente de la plataforma.
29.4.1) Obtener el directorio de trabajo actual
El directorio de trabajo actual (CWD) es la carpeta que tu programa considera su punto de inicio para rutas relativas:
import os
cwd = os.getcwd()
print(f"Current working directory: {cwd}")Output (example):
Current working directory: /home/alice/projects/myappCuando abres un archivo con una ruta relativa como 'data.txt', Python busca en el directorio de trabajo actual. Comprender el CWD ayuda a depurar errores de "archivo no encontrado".
29.4.2) Cambiar el directorio actual
Puedes cambiar el directorio de trabajo con os.chdir():
import os
original = os.getcwd()
print("Original directory:", original)
# Cambiar a un directorio diferente
os.chdir('/tmp')
print("New directory:", os.getcwd())
# Volver atrás
os.chdir(original)
print("Back to:", os.getcwd())Output:
Original directory: /home/alice/projects
New directory: /tmp
Back to: /home/alice/projectsNota: En el Capítulo 28, aprendiste sobre contextlib.chdir(), que restaura automáticamente el directorio original. Para cambios simples de directorio, prefiere usar el gestor de contexto:
from contextlib import chdir
with chdir('/tmp'):
print("Temporarily in:", os.getcwd())
# Restaurado automáticamenteEsto garantiza que el directorio siempre se restaure, incluso si ocurre un error.
29.4.3) Construir rutas con os.path.join()
Los distintos sistemas operativos usan distintos separadores de ruta:
- Unix/Linux/macOS:
/(barra inclinada) - Windows:
\(barra invertida)
os.path.join() construye rutas correctamente para la plataforma actual:
import os
# Construir una ruta hacia un archivo en un subdirectorio
data_dir = 'data'
filename = 'users.txt'
filepath = os.path.join(data_dir, filename)
print(f"File path: {filepath}")Output (on Unix/Linux/macOS):
File path: data/users.txtOutput (on Windows):
File path: data\users.txtPuedes unir varios componentes:
import os
base = '/home/alice'
project = 'myapp'
subdir = 'data'
file = 'config.json'
full_path = os.path.join(base, project, subdir, file)
print(full_path) # Output: /home/alice/myapp/data/config.jsonUsar os.path.join() hace que tu código sea portátil entre sistemas operativos.
29.4.4) Comprobar si existen rutas
Antes de trabajar con archivos o directorios, comprueba si existen:
import os
path = 'data.txt'
if os.path.exists(path):
print(f"'{path}' exists")
else:
print(f"'{path}' does not exist")También puedes comprobar específicamente si son archivos o directorios:
import os
path = 'mydir'
if os.path.isfile(path):
print(f"'{path}' is a file")
elif os.path.isdir(path):
print(f"'{path}' is a directory")
elif os.path.exists(path):
print(f"'{path}' exists but is neither a file nor directory")
else:
print(f"'{path}' does not exist")Estas comprobaciones evitan errores al intentar abrir archivos inexistentes o listar directorios inexistentes.
29.4.5) Obtener rutas absolutas
os.path.abspath() convierte rutas relativas en rutas absolutas:
import os
relative_path = 'data/users.txt'
absolute_path = os.path.abspath(relative_path)
print(f"Relative: {relative_path}")
print(f"Absolute: {absolute_path}")Output (example):
Relative: data/users.txt
Absolute: /home/alice/projects/myapp/data/users.txtEsto es útil para registro, mensajes de error o cuando necesitas saber la ubicación exacta de un archivo.
29.4.6) Dividir rutas en componentes
os.path.split() separa una ruta en directorio y nombre de archivo:
import os
path = '/home/alice/projects/data.txt'
directory, filename = os.path.split(path)
print(f"Directory: {directory}")
print(f"Filename: {filename}")Output:
Directory: /home/alice/projects
Filename: data.txtos.path.basename() obtiene solo el nombre del archivo, y os.path.dirname() obtiene solo el directorio:
import os
path = '/home/alice/projects/data.txt'
print(f"Basename: {os.path.basename(path)}") # Output: data.txt
print(f"Dirname: {os.path.dirname(path)}") # Output: /home/alice/projects29.4.7) Dividir extensiones de archivo
os.path.splitext() separa el nombre del archivo de su extensión:
import os
filename = 'report.pdf'
name, extension = os.path.splitext(filename)
print(f"Name: {name}") # Output: report
print(f"Extension: {extension}") # Output: .pdfEsto es útil para procesar archivos según su tipo:
import os
files = ['data.csv', 'image.png', 'document.txt', 'script.py']
for file in files:
name, ext = os.path.splitext(file)
if ext == '.csv':
print(f"Process CSV file: {file}")
elif ext == '.png':
print(f"Process image file: {file}")Output:
Process CSV file: data.csv
Process image file: image.png29.5) Listar, crear y eliminar archivos y directorios
El módulo os proporciona funciones para manipular el sistema de archivos: listar el contenido de directorios, crear nuevos directorios y eliminar archivos y carpetas.
29.5.1) Listar el contenido de un directorio
os.listdir() devuelve una lista de todos los elementos en un directorio:
import os
# Listar el contenido del directorio actual
contents = os.listdir('.')
print("Current directory contents:")
for item in contents:
print(f" {item}")Output (example):
Current directory contents:
script.py
data.txt
mydir
README.mdLa lista incluye tanto archivos como directorios. Para distinguirlos:
import os
contents = os.listdir('.')
print("Files:")
for item in contents:
if os.path.isfile(item):
print(f" {item}")
print("\nDirectories:")
for item in contents:
if os.path.isdir(item):
print(f" {item}")Output:
Files:
script.py
data.txt
README.md
Directories:
mydir29.5.2) Crear directorios
os.mkdir() crea un único directorio:
import os
new_dir = 'output'
if not os.path.exists(new_dir):
os.mkdir(new_dir)
print(f"Created directory: {new_dir}")
else:
print(f"Directory already exists: {new_dir}")Output:
Created directory: outputImportante: os.mkdir() falla si el directorio padre no existe. Por ejemplo, intentar crear 'data/output' cuando 'data' no existe generará un error.
os.makedirs() crea todos los directorios padre necesarios:
import os
nested_dir = 'data/processed/2024'
if not os.path.exists(nested_dir):
os.makedirs(nested_dir)
print(f"Created directory structure: {nested_dir}")Output:
Created directory structure: data/processed/2024Esto crea data, luego data/processed, y luego data/processed/2024 si no existen.
29.5.3) Eliminar archivos
os.remove() elimina un archivo:
import os
filename = 'temp.txt'
# Crear un archivo temporal
with open(filename, 'w') as f:
f.write('Temporary data')
print(f"File exists: {os.path.exists(filename)}") # Output: True
# Eliminar el archivo
os.remove(filename)
print(f"File exists: {os.path.exists(filename)}") # Output: FalseAdvertencia: os.remove() elimina archivos de forma permanente; no van a una papelera de reciclaje.
29.5.4) Eliminar directorios
os.rmdir() elimina un directorio vacío:
import os
directory = 'empty_dir'
# Crear y luego eliminar un directorio vacío
os.mkdir(directory)
print(f"Created: {directory}")
os.rmdir(directory)
print(f"Removed: {directory}")os.rmdir() falla si el directorio contiene archivos. Para eliminar un directorio y todo su contenido, necesitas eliminar los archivos primero:
import os
def remove_directory_contents(directory):
"""Eliminar todos los archivos de un directorio y luego eliminar el directorio.
Nota: falla si el directorio contiene subdirectorios.
"""
if not os.path.exists(directory):
print(f"Directory does not exist: {directory}")
return
# Eliminar todos los archivos del directorio
for item in os.listdir(directory):
item_path = os.path.join(directory, item)
if os.path.isfile(item_path):
os.remove(item_path)
print(f"Removed file: {item_path}")
# Eliminar el directorio ahora vacío
os.rmdir(directory)
print(f"Removed directory: {directory}")
# Ejemplo de uso
test_dir = 'test_data'
os.makedirs(test_dir, exist_ok=True)
# Crear algunos archivos de prueba
with open(os.path.join(test_dir, 'file1.txt'), 'w') as f:
f.write('test')
with open(os.path.join(test_dir, 'file2.txt'), 'w') as f:
f.write('test')
# Eliminar todo
remove_directory_contents(test_dir)Output:
Removed file: test_data/file1.txt
Removed file: test_data/file2.txt
Removed directory: test_dataNota: Para una eliminación de directorios más compleja (incluyendo subdirectorios), el módulo shutil proporciona shutil.rmtree(), pero eso está fuera de nuestro alcance actual.
29.5.5) Renombrar archivos y directorios
os.rename() renombra o mueve archivos y directorios:
import os
# Renombrar un archivo
old_name = 'draft.txt'
new_name = 'final.txt'
# Crear archivo de prueba
with open(old_name, 'w') as f:
f.write('content')
os.rename(old_name, new_name)
print(f"Renamed '{old_name}' to '{new_name}'")También puedes mover archivos a diferentes directorios:
import os
# Crear directorios y archivo
os.makedirs('source', exist_ok=True)
os.makedirs('destination', exist_ok=True)
with open('source/file.txt', 'w') as f:
f.write('content')
# Mover archivo a un directorio diferente
os.rename('source/file.txt', 'destination/file.txt')
print("Moved file to destination directory")Los módulos sys y os le dan a tus programas de Python la capacidad de interactuar con el sistema operativo, aceptar entrada de la línea de comandos, leer configuración y gestionar archivos y directorios. Estas capacidades transforman scripts simples en potentes herramientas de línea de comandos que se integran sin problemas con el entorno más amplio del sistema.