Python
Tutorial Python: Módulo sys
Aprende a usar el módulo sys en Python para manejar argumentos, rutas de importación y controlar la salida del programa de forma profesional.
Aprende Python y certifícateArgumentos de línea de comandos
El módulo sys
de Python proporciona acceso a variables y funciones que interactúan directamente con el intérprete. Una de sus funcionalidades más útiles es el acceso a los argumentos de línea de comandos a través de sys.argv
, que permite que nuestros programas reciban información cuando son ejecutados desde la terminal.
Cuando ejecutamos un script de Python desde la línea de comandos, podemos pasarle parámetros adicionales que el programa puede utilizar para modificar su comportamiento. Estos parámetros se capturan en una lista llamada sys.argv
.
Estructura básica de sys.argv
Para empezar a trabajar con argumentos de línea de comandos, primero necesitamos importar el módulo sys
:
import sys
La lista sys.argv
siempre contiene al menos un elemento: el nombre del script que se está ejecutando. Los argumentos adicionales se añaden a continuación:
# archivo: mostrar_args.py
import sys
print(f"Nombre del script: {sys.argv[0]}")
print(f"Número de argumentos: {len(sys.argv) - 1}")
print(f"Argumentos: {sys.argv[1:]}")
Si ejecutamos este script con argumentos adicionales:
python mostrar_args.py uno dos tres
Obtendremos una salida similar a:
Nombre del script: mostrar_args.py
Número de argumentos: 3
Argumentos: ['uno', 'dos', 'tres']
Procesamiento básico de argumentos
Los argumentos en sys.argv
siempre se reciben como cadenas de texto, independientemente de su contenido. Si necesitamos trabajar con números u otros tipos de datos, debemos realizar la conversión manualmente:
# archivo: calculadora.py
import sys
if len(sys.argv) != 4:
print("Uso: python calculadora.py <número1> <operación> <número2>")
sys.exit(1)
try:
num1 = float(sys.argv[1])
operacion = sys.argv[2]
num2 = float(sys.argv[3])
if operacion == "+":
resultado = num1 + num2
elif operacion == "-":
resultado = num1 - num2
elif operacion == "*":
resultado = num1 * num2
elif operacion == "/":
if num2 == 0:
print("Error: División por cero")
sys.exit(1)
resultado = num1 / num2
else:
print(f"Operación no soportada: {operacion}")
sys.exit(1)
print(f"Resultado: {resultado}")
except ValueError:
print("Error: Los argumentos deben ser números válidos")
sys.exit(1)
Este script implementa una calculadora simple que acepta dos números y una operación. Podemos usarlo así:
python calculadora.py 5 + 3
Y obtendremos:
Resultado: 8.0
Patrones comunes para argumentos
Existen varios patrones comunes para trabajar con argumentos de línea de comandos:
- Argumentos posicionales: El orden de los argumentos determina su significado.
- Argumentos con banderas: Utilizan prefijos como
-
o--
para identificar opciones. - Argumentos con valores: Una bandera seguida de un valor (por ejemplo,
--output archivo.txt
).
Veamos un ejemplo que combina estos patrones:
# archivo: procesador.py
import sys
def mostrar_ayuda():
print("Uso: python procesador.py [opciones] archivo")
print("Opciones:")
print(" -h, --help Muestra esta ayuda")
print(" -v, --verbose Muestra información detallada")
print(" -o ARCHIVO Especifica archivo de salida")
sys.exit(0)
# Valores predeterminados
verbose = False
archivo_salida = None
archivos_entrada = []
# Procesar argumentos
i = 1
while i < len(sys.argv):
arg = sys.argv[i]
if arg in ("-h", "--help"):
mostrar_ayuda()
elif arg in ("-v", "--verbose"):
verbose = True
elif arg == "-o" and i + 1 < len(sys.argv):
archivo_salida = sys.argv[i + 1]
i += 1 # Saltar el siguiente argumento
elif arg.startswith("-"):
print(f"Opción desconocida: {arg}")
sys.exit(1)
else:
# No es una opción, debe ser un archivo de entrada
archivos_entrada.append(arg)
i += 1
# Verificar que se proporcionó al menos un archivo de entrada
if not archivos_entrada:
print("Error: Debe especificar al menos un archivo de entrada")
mostrar_ayuda()
# Mostrar configuración
if verbose:
print("Configuración:")
print(f" Modo detallado: Activado")
print(f" Archivo de salida: {archivo_salida or 'Salida estándar'}")
print(f" Archivos de entrada: {', '.join(archivos_entrada)}")
# Aquí iría el código para procesar los archivos
print(f"Procesando {len(archivos_entrada)} archivo(s)...")
Este script puede usarse de varias formas:
python procesador.py -v -o resultado.txt datos.csv
python procesador.py --help
python procesador.py archivo1.txt archivo2.txt
Uso de argparse para argumentos más complejos
Aunque sys.argv
es útil para casos simples, Python incluye el módulo argparse
en su biblioteca estándar para manejar argumentos de forma más robusta. Este módulo simplifica la validación, documentación y procesamiento de argumentos complejos:
# archivo: conversor.py
import sys
import argparse
def main():
# Crear el analizador de argumentos
parser = argparse.ArgumentParser(
description="Convierte temperaturas entre Celsius y Fahrenheit"
)
# Añadir argumentos
parser.add_argument(
"temperatura",
type=float,
help="Valor de temperatura a convertir"
)
parser.add_argument(
"-f", "--fahrenheit",
action="store_true",
help="Convertir de Fahrenheit a Celsius (por defecto: Celsius a Fahrenheit)"
)
parser.add_argument(
"-p", "--precision",
type=int,
default=2,
help="Número de decimales en el resultado (por defecto: 2)"
)
# Analizar los argumentos
args = parser.parse_args()
# Realizar la conversión
if args.fahrenheit:
# Convertir de Fahrenheit a Celsius
resultado = (args.temperatura - 32) * 5/9
origen = "Fahrenheit"
destino = "Celsius"
else:
# Convertir de Celsius a Fahrenheit
resultado = (args.temperatura * 9/5) + 32
origen = "Celsius"
destino = "Fahrenheit"
# Mostrar el resultado con la precisión especificada
print(f"{args.temperatura}° {origen} = {resultado:.{args.precision}f}° {destino}")
if __name__ == "__main__":
main()
Este conversor de temperaturas puede usarse de varias formas:
python conversor.py 25 # Convierte 25°C a Fahrenheit
python conversor.py -f 98.6 # Convierte 98.6°F a Celsius
python conversor.py 100 -p 4 # Muestra el resultado con 4 decimales
Consejos prácticos para trabajar con argumentos
- Validación temprana: Verifica que los argumentos sean válidos al inicio del programa.
- Mensajes de error claros: Proporciona mensajes de error descriptivos cuando faltan argumentos o tienen formato incorrecto.
- Documentación integrada: Incluye ayuda sobre cómo usar tu programa con la opción
-h
o--help
. - Valores predeterminados: Define comportamientos predeterminados razonables cuando no se proporcionan ciertos argumentos.
- Códigos de salida: Utiliza
sys.exit(0)
para una salida exitosa ysys.exit(1)
(u otro número distinto de cero) para indicar errores.
Ejemplo práctico: Procesador de archivos
Veamos un ejemplo más completo que procesa archivos de texto:
# archivo: contador_palabras.py
import sys
import os
def contar_palabras(archivo):
"""Cuenta las palabras en un archivo de texto."""
try:
with open(archivo, 'r', encoding='utf-8') as f:
contenido = f.read()
palabras = contenido.split()
return len(palabras)
except FileNotFoundError:
print(f"Error: No se encontró el archivo '{archivo}'")
return None
except Exception as e:
print(f"Error al procesar '{archivo}': {e}")
return None
def main():
# Verificar argumentos
if len(sys.argv) < 2:
print("Uso: python contador_palabras.py <archivo1> [archivo2 ...]")
sys.exit(1)
archivos = sys.argv[1:]
total_palabras = 0
archivos_procesados = 0
# Procesar cada archivo
for archivo in archivos:
if not os.path.exists(archivo):
print(f"Advertencia: El archivo '{archivo}' no existe")
continue
if os.path.isdir(archivo):
print(f"Advertencia: '{archivo}' es un directorio, no un archivo")
continue
palabras = contar_palabras(archivo)
if palabras is not None:
print(f"{archivo}: {palabras} palabras")
total_palabras += palabras
archivos_procesados += 1
# Mostrar resumen
if archivos_procesados > 0:
print(f"\nResumen: {total_palabras} palabras en {archivos_procesados} archivo(s)")
print(f"Promedio: {total_palabras / archivos_procesados:.1f} palabras por archivo")
else:
print("\nNo se procesó ningún archivo correctamente")
sys.exit(1)
if __name__ == "__main__":
main()
Este script cuenta las palabras en uno o más archivos de texto y muestra estadísticas:
python contador_palabras.py README.md docs/manual.txt notas.txt
La salida podría ser:
README.md: 245 palabras
docs/manual.txt: 1203 palabras
Advertencia: El archivo 'notas.txt' no existe
Resumen: 1448 palabras en 2 archivo(s)
Promedio: 724.0 palabras por archivo
Variables y funciones del sistema
El módulo sys
proporciona acceso a variables y funciones que interactúan directamente con el intérprete de Python y el sistema operativo subyacente. Estas herramientas nos permiten obtener información sobre el entorno de ejecución y modificar ciertos aspectos del comportamiento del intérprete.
Para utilizar estas funcionalidades, primero debemos importar el módulo:
import sys
Información sobre la versión de Python
El módulo sys
nos permite acceder a información detallada sobre la versión de Python que estamos utilizando:
import sys
# Versión de Python como cadena de texto
print(f"Versión de Python: {sys.version}")
# Información de versión como tupla (major, minor, micro, nivel, serial)
print(f"Versión como tupla: {sys.version_info}")
# Acceso a componentes individuales de la versión
print(f"Versión principal: {sys.version_info.major}")
print(f"Versión secundaria: {sys.version_info.minor}")
Esta información es útil para escribir código compatible con diferentes versiones de Python o para verificar requisitos mínimos:
import sys
# Verificar si estamos usando Python 3.8 o superior
if sys.version_info >= (3, 8):
print("Características de Python 3.8+ disponibles")
else:
print("Se requiere Python 3.8 o superior")
sys.exit(1)
Plataforma y entorno de ejecución
El módulo sys
también proporciona información sobre la plataforma en la que se está ejecutando Python:
import sys
# Sistema operativo (posix, nt, java)
print(f"Plataforma: {sys.platform}")
# Tamaño de palabra del sistema (32 o 64 bits)
print(f"Tamaño de palabra: {sys.maxsize.bit_length() + 1} bits")
# Codificación predeterminada para archivos
print(f"Codificación predeterminada: {sys.getdefaultencoding()}")
Estas variables son útiles para escribir código que se comporte de manera diferente según la plataforma:
import sys
# Detectar el sistema operativo
if sys.platform.startswith('win'):
config_path = 'C:\\config.ini'
print("Sistema Windows detectado")
elif sys.platform.startswith('darwin'):
config_path = '/Users/usuario/config.ini'
print("Sistema macOS detectado")
else:
config_path = '/etc/app/config.ini'
print("Sistema Unix/Linux detectado")
Entrada y salida estándar
El módulo sys
proporciona acceso a los flujos estándar de entrada, salida y error:
sys.stdin
: Flujo de entrada estándarsys.stdout
: Flujo de salida estándarsys.stderr
: Flujo de error estándar
Estos objetos son archivos especiales que podemos usar para leer la entrada del usuario o escribir salida:
import sys
# Leer una línea de la entrada estándar
print("Ingrese su nombre:")
nombre = sys.stdin.readline().strip()
# Escribir en la salida estándar
sys.stdout.write(f"Hola, {nombre}!\n")
# Escribir en el flujo de error
sys.stderr.write("Este es un mensaje de error\n")
Una aplicación común es redirigir temporalmente la salida estándar a un archivo:
import sys
# Guardar la referencia original a stdout
stdout_original = sys.stdout
try:
# Redirigir stdout a un archivo
with open('salida.log', 'w') as archivo:
sys.stdout = archivo
print("Este texto se escribe en el archivo")
print("También este texto")
finally:
# Restaurar stdout a su valor original
sys.stdout = stdout_original
print("Este texto se muestra en la consola")
Control de memoria y recursos
El módulo sys
proporciona funciones para gestionar la memoria y otros recursos del sistema:
import sys
# Tamaño de un objeto en bytes
size = sys.getsizeof([1, 2, 3, 4, 5])
print(f"Tamaño de la lista: {size} bytes")
# Obtener el conteo de referencias de un objeto
lista = [1, 2, 3]
otra_referencia = lista
print(f"Referencias a la lista: {sys.getrefcount(lista) - 1}") # Restamos 1 por la referencia temporal en getrefcount
También podemos controlar el recolector de basura de Python:
import sys
import gc
# Desactivar el recolector de basura automático
gc.disable()
# Realizar operaciones intensivas...
datos = [list(range(1000)) for _ in range(100)]
# Forzar una recolección de basura manual
n = gc.collect()
print(f"Se liberaron {n} objetos")
# Reactivar el recolector de basura
gc.enable()
Variables de configuración del intérprete
El módulo sys
permite acceder y modificar varias configuraciones del intérprete:
import sys
# Límite de recursión actual
print(f"Límite de recursión: {sys.getrecursionlimit()}")
# Aumentar el límite de recursión
sys.setrecursionlimit(3000)
print(f"Nuevo límite: {sys.getrecursionlimit()}")
# Obtener el tamaño máximo de cadena mostrada en la representación de objetos
print(f"Límite de visualización: {sys.getsizeof('x' * 100)}")
Un uso común es modificar el límite de recursión para algoritmos que requieren muchas llamadas recursivas:
import sys
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
# Aumentar el límite para cálculos recursivos profundos
sys.setrecursionlimit(3000)
try:
resultado = factorial(1000)
print(f"Factorial calculado correctamente")
except RecursionError:
print("La recursión fue demasiado profunda")
Módulos cargados y rutas de búsqueda
Podemos examinar qué módulos están actualmente cargados y dónde Python busca módulos:
import sys
import math
import random
# Ver todos los módulos cargados
print("Módulos cargados:")
for nombre, modulo in sys.modules.items():
if nombre in ['math', 'random', 'sys']:
print(f" - {nombre}: {modulo}")
# Verificar si un módulo está cargado
if 'numpy' in sys.modules:
print("NumPy está cargado")
else:
print("NumPy no está cargado")
Funciones de finalización y hooks
El módulo sys
proporciona mecanismos para registrar funciones de finalización que se ejecutarán cuando el programa termine:
import sys
import atexit
def limpiar_recursos():
print("Limpiando recursos...")
# Aquí iría el código para cerrar archivos, conexiones, etc.
# Registrar función para ejecutarse al salir
atexit.register(limpiar_recursos)
# También podemos registrar funciones para ejecutarse cuando ocurran excepciones no capturadas
def manejar_excepcion(tipo, valor, traceback):
print(f"Error no capturado: {valor}")
# Registrar el error, notificar, etc.
sys.__excepthook__(tipo, valor, traceback) # Llamar al manejador original
# Reemplazar el manejador de excepciones predeterminado
sys.excepthook = manejar_excepcion
# Simular un error
print("Programa en ejecución...")
# Descomentar para probar: 1/0 # Esto generará una excepción ZeroDivisionError
Monitoreo de uso de memoria
Podemos usar sys
para monitorear el uso de memoria de nuestras aplicaciones:
import sys
def mostrar_uso_memoria(descripcion):
# Obtener información sobre objetos en memoria
objetos = {}
for modulo in sys.modules.values():
for nombre in dir(modulo):
obj = getattr(modulo, nombre)
objetos[id(obj)] = obj
# Contar tipos de objetos
tipos = {}
for obj in objetos.values():
tipo = type(obj).__name__
tipos[tipo] = tipos.get(tipo, 0) + 1
# Mostrar los 5 tipos más comunes
print(f"\nUso de memoria ({descripcion}):")
for tipo, cantidad in sorted(tipos.items(), key=lambda x: x[1], reverse=True)[:5]:
print(f" - {tipo}: {cantidad} instancias")
# Monitorear antes y después de una operación intensiva
mostrar_uso_memoria("Inicial")
# Crear muchos objetos
grandes_listas = [list(range(10000)) for _ in range(10)]
mostrar_uso_memoria("Después de crear listas")
# Liberar memoria
grandes_listas = None
mostrar_uso_memoria("Después de liberar")
Ejemplo práctico: Monitor de recursos
Veamos un ejemplo más completo que monitorea el uso de recursos de un programa:
import sys
import time
import os
import psutil # Requiere: pip install psutil
def monitor_recursos(intervalo=1.0, duracion=10):
"""Monitorea el uso de recursos del proceso actual."""
proceso = psutil.Process(os.getpid())
print(f"Monitoreando recursos del proceso {os.getpid()}:")
print(f"Python {sys.version.split()[0]} en {sys.platform}")
print(f"{'Tiempo (s)':<10} {'CPU %':<8} {'Memoria (MB)':<12} {'Objetos':<10}")
print("-" * 42)
inicio = time.time()
while time.time() - inicio < duracion:
# Obtener uso de CPU y memoria
cpu_percent = proceso.cpu_percent()
memoria = proceso.memory_info().rss / (1024 * 1024) # Convertir a MB
# Contar objetos (simplificado)
objetos = len(gc.get_objects())
# Mostrar estadísticas
tiempo_transcurrido = time.time() - inicio
print(f"{tiempo_transcurrido:<10.1f} {cpu_percent:<8.1f} {memoria:<12.2f} {objetos:<10}")
time.sleep(intervalo)
# Ejemplo de uso
if __name__ == "__main__":
import gc
# Crear algunos datos para ver el efecto
datos = []
# Iniciar monitoreo
monitor_recursos(intervalo=0.5, duracion=3)
# Crear más datos y ver el efecto
for i in range(5):
datos.append([0] * 1000000) # Crear una lista grande
print(f"Creada lista grande #{i+1}")
monitor_recursos(intervalo=0.5, duracion=1)
# Liberar memoria
datos = None
gc.collect()
print("Memoria liberada")
monitor_recursos(intervalo=0.5, duracion=2)
Este script muestra cómo podemos usar sys
junto con otras bibliotecas para monitorear el comportamiento de nuestras aplicaciones y detectar problemas como fugas de memoria o uso excesivo de CPU.
Manejo de rutas de importación
El módulo sys
proporciona herramientas fundamentales para gestionar las rutas de importación en Python, permitiéndonos controlar dónde y cómo el intérprete busca los módulos que intentamos importar. Entender este mecanismo es esencial para desarrollar aplicaciones modulares y mantener una estructura de proyecto organizada.
Cuando importamos un módulo en Python, el intérprete busca en una lista ordenada de directorios para encontrarlo. Esta lista se almacena en la variable sys.path
, que podemos examinar y modificar durante la ejecución del programa.
Explorando sys.path
Para entender cómo Python encuentra los módulos, podemos examinar el contenido de sys.path
:
import sys
# Mostrar todas las rutas de búsqueda
for i, ruta in enumerate(sys.path):
print(f"{i}: {ruta}")
Al ejecutar este código, veremos una lista de directorios que típicamente incluye:
- El directorio del script que se está ejecutando (o directorio actual si estamos en modo interactivo)
- Los directorios especificados en la variable de entorno PYTHONPATH
- Directorios de instalación estándar de Python (bibliotecas del sistema)
- Directorios de paquetes instalados (como los instalados con pip)
Modificando las rutas de importación
Podemos modificar dinámicamente sys.path
para incluir directorios adicionales donde Python buscará módulos:
import sys
import os
# Añadir un directorio al inicio de sys.path
directorio_modulos = os.path.join(os.path.dirname(__file__), "modulos")
sys.path.insert(0, directorio_modulos)
# Ahora podemos importar módulos desde ese directorio
import mi_modulo_personalizado
Las operaciones más comunes para modificar sys.path
son:
sys.path.append(ruta)
: Añade una ruta al final de la listasys.path.insert(0, ruta)
: Añade una ruta al principio de la lista (mayor prioridad)sys.path.remove(ruta)
: Elimina una ruta de la lista
Patrones de uso comunes
1. Importar módulos desde directorios hermanos
Un problema común es importar módulos que están en directorios adyacentes. Por ejemplo, con esta estructura:
proyecto/
├── modulo_a/
│ └── script_a.py
└── modulo_b/
└── script_b.py
Si queremos importar script_b.py
desde script_a.py
, podemos hacer:
# En script_a.py
import sys
import os
# Obtener la ruta al directorio raíz del proyecto
directorio_raiz = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, directorio_raiz)
# Ahora podemos importar usando rutas relativas al directorio raíz
from modulo_b import script_b
2. Crear un entorno de pruebas aislado
Podemos manipular sys.path
para crear un entorno de pruebas que use versiones específicas de módulos:
import sys
import os
# Guardar el path original
path_original = sys.path.copy()
# Configurar un entorno aislado para pruebas
def configurar_entorno_pruebas():
# Limpiar sys.path y añadir solo lo necesario
sys.path = [os.path.abspath("./modulos_prueba")]
sys.path.append(os.path.abspath("./mocks"))
# Ahora las importaciones usarán nuestras versiones de prueba
def restaurar_entorno():
# Restaurar sys.path a su estado original
sys.path = path_original
# Ejemplo de uso
try:
configurar_entorno_pruebas()
import modulo_a # Usará la versión de prueba
# Ejecutar pruebas...
finally:
restaurar_entorno()
3. Importación condicional basada en disponibilidad
Podemos usar sys.path
junto con manejo de excepciones para implementar importaciones alternativas:
import sys
import os
try:
# Intentar importar un módulo
import biblioteca_rapida
except ImportError:
# Si no está disponible, añadir ruta a nuestra implementación alternativa
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "alternativas"))
import biblioteca_alternativa as biblioteca_rapida
print("Usando implementación alternativa")
Herramientas para trabajar con rutas de importación
Verificar si un módulo puede ser importado
Podemos verificar si un módulo está disponible en las rutas de importación sin intentar importarlo directamente:
import sys
import importlib.util
def modulo_disponible(nombre_modulo):
"""Verifica si un módulo puede ser importado sin importarlo realmente."""
spec = importlib.util.find_spec(nombre_modulo)
return spec is not None
# Ejemplo de uso
if modulo_disponible("numpy"):
print("NumPy está disponible para importar")
else:
print("NumPy no está disponible")
Encontrar la ruta de un módulo importado
Podemos determinar desde qué archivo se importó un módulo específico:
import sys
import os
import importlib.util
def encontrar_ruta_modulo(nombre_modulo):
"""Encuentra la ruta del archivo de un módulo sin importarlo."""
spec = importlib.util.find_spec(nombre_modulo)
if spec is None:
return None
return spec.origin
# Ejemplo de uso
ruta_os = encontrar_ruta_modulo("os")
print(f"El módulo 'os' se encuentra en: {ruta_os}")
Aplicaciones prácticas
Crear un cargador de plugins
Podemos usar sys.path
para implementar un sistema de plugins que busque módulos en múltiples directorios:
import sys
import os
import importlib
class CargadorPlugins:
def __init__(self):
self.plugins = {}
self.directorios_plugins = []
def agregar_directorio(self, directorio):
"""Añade un directorio a la búsqueda de plugins."""
ruta_abs = os.path.abspath(directorio)
if os.path.isdir(ruta_abs) and ruta_abs not in self.directorios_plugins:
self.directorios_plugins.append(ruta_abs)
def descubrir_plugins(self):
"""Busca plugins en todos los directorios registrados."""
# Guardar el path original
path_original = sys.path.copy()
try:
for directorio in self.directorios_plugins:
# Añadir temporalmente el directorio al path
if directorio not in sys.path:
sys.path.insert(0, directorio)
# Buscar archivos Python en el directorio
for archivo in os.listdir(directorio):
if archivo.endswith('.py') and not archivo.startswith('_'):
nombre_modulo = archivo[:-3] # Quitar la extensión .py
try:
# Importar el módulo dinámicamente
modulo = importlib.import_module(nombre_modulo)
# Verificar si es un plugin válido (tiene función 'ejecutar')
if hasattr(modulo, 'ejecutar'):
self.plugins[nombre_modulo] = modulo
print(f"Plugin cargado: {nombre_modulo}")
except ImportError as e:
print(f"Error al cargar plugin {nombre_modulo}: {e}")
finally:
# Restaurar el path original
sys.path = path_original
def ejecutar_plugin(self, nombre, *args, **kwargs):
"""Ejecuta un plugin específico."""
if nombre in self.plugins:
return self.plugins[nombre].ejecutar(*args, **kwargs)
else:
raise ValueError(f"Plugin no encontrado: {nombre}")
# Ejemplo de uso
if __name__ == "__main__":
cargador = CargadorPlugins()
cargador.agregar_directorio("./plugins")
cargador.agregar_directorio("./plugins_externos")
cargador.descubrir_plugins()
# Ejecutar un plugin si está disponible
if "conversor_texto" in cargador.plugins:
resultado = cargador.ejecutar_plugin("conversor_texto",
texto="Hola Mundo",
mayusculas=True)
print(f"Resultado: {resultado}")
Implementar un importador personalizado
Para casos avanzados, podemos crear un importador personalizado que modifique cómo Python busca y carga módulos:
import sys
import importlib.abc
import importlib.machinery
class ImportadorComprimido(importlib.abc.MetaPathFinder):
"""Importador personalizado que puede cargar módulos desde archivos ZIP."""
def __init__(self, archivo_zip):
self.archivo_zip = archivo_zip
# Aquí iría la lógica para acceder al contenido del ZIP
def find_spec(self, fullname, path, target=None):
# Verificar si el módulo está en nuestro archivo ZIP
if self._tiene_modulo(fullname):
# Crear y devolver un spec para el módulo
return importlib.machinery.ModuleSpec(
name=fullname,
loader=self._crear_loader(fullname),
origin=f"zip:{self.archivo_zip}:/{fullname.replace('.', '/')}.py"
)
return None
def _tiene_modulo(self, nombre):
# Verificar si el módulo existe en el ZIP
# (Implementación simplificada)
return True
def _crear_loader(self, nombre):
# Crear un loader para el módulo
# (Implementación simplificada)
return importlib.abc.Loader()
# Registrar el importador personalizado
sys.meta_path.insert(0, ImportadorComprimido("modulos.zip"))
# Ahora podemos importar módulos desde el archivo ZIP
try:
import modulo_comprimido
print("Módulo importado correctamente desde ZIP")
except ImportError as e:
print(f"Error al importar: {e}")
Mejores prácticas
Al trabajar con rutas de importación, es recomendable seguir estas prácticas:
- Evita modificar
sys.path
en código de producción cuando sea posible. En su lugar, utiliza paquetes correctamente estructurados. - Usa modificaciones temporales de
sys.path
con bloquestry/finally
para restaurar el estado original. - Prefiere rutas absolutas sobre rutas relativas al modificar
sys.path
para evitar problemas con el directorio de trabajo. - Documenta claramente cualquier modificación de
sys.path
para facilitar el mantenimiento. - Considera alternativas como
PYTHONPATH
o archivos.pth
para configuraciones permanentes.
# Ejemplo de modificación temporal y segura de sys.path
import sys
import os
import contextlib
@contextlib.contextmanager
def ruta_temporal(directorio):
"""Añade temporalmente un directorio a sys.path y lo restaura después."""
ruta_original = sys.path.copy()
try:
sys.path.insert(0, os.path.abspath(directorio))
yield
finally:
sys.path = ruta_original
# Uso con administrador de contexto
with ruta_temporal("./lib_externa"):
import modulo_especial
resultado = modulo_especial.procesar_datos()
# Aquí sys.path ya está restaurado
El manejo adecuado de las rutas de importación es una habilidad fundamental para desarrollar aplicaciones Python modulares y mantenibles, especialmente en proyectos grandes o cuando se trabaja con dependencias complejas.
Salida del programa
El módulo sys
proporciona varias funcionalidades para controlar la salida del programa en Python, permitiéndonos gestionar cómo y cuándo nuestro programa finaliza su ejecución. Estas herramientas son fundamentales para desarrollar aplicaciones robustas que puedan comunicar correctamente su estado de finalización al sistema operativo.
Terminación controlada con sys.exit()
La función sys.exit()
es la forma recomendada para terminar la ejecución de un programa Python de manera controlada:
import sys
def procesar_archivo(ruta):
try:
with open(ruta, 'r') as archivo:
contenido = archivo.read()
return contenido
except FileNotFoundError:
print(f"Error: No se encontró el archivo '{ruta}'")
sys.exit(1) # Terminar con código de error
# Ejemplo de uso
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Uso: python script.py <archivo>")
sys.exit(1) # Terminar con código de error
datos = procesar_archivo(sys.argv[1])
print("Procesamiento completado con éxito")
sys.exit(0) # Terminar con éxito
La función sys.exit()
acepta un argumento opcional que representa el código de salida:
sys.exit(0)
: Indica una terminación exitosa (valor predeterminado si no se especifica)sys.exit(1)
o cualquier número distinto de cero: Indica una terminación con error
Estos códigos de salida son importantes cuando nuestros scripts son utilizados por otros programas o en scripts de shell, ya que permiten determinar si la ejecución fue exitosa.
Códigos de salida convencionales
Aunque Python no impone reglas estrictas sobre los códigos de salida, existen convenciones comunes:
import sys
# Códigos de salida comunes
EXIT_SUCCESS = 0 # Éxito
EXIT_FAILURE = 1 # Error general
EXIT_USAGE = 2 # Error de uso incorrecto (argumentos)
EXIT_DATA_ERROR = 65 # Error en los datos de entrada
EXIT_NO_INPUT = 66 # No se puede abrir la entrada
EXIT_NO_USER = 67 # Usuario no existe
# Ejemplo de uso
def validar_usuario(nombre):
usuarios_validos = ["admin", "usuario1", "usuario2"]
if not nombre:
print("Error: Debe proporcionar un nombre de usuario")
sys.exit(EXIT_USAGE)
if nombre not in usuarios_validos:
print(f"Error: El usuario '{nombre}' no existe")
sys.exit(EXIT_NO_USER)
return True
# Uso del programa
if len(sys.argv) < 2:
print("Uso: python script.py <usuario>")
sys.exit(EXIT_USAGE)
validar_usuario(sys.argv[1])
print("Usuario válido, continuando...")
sys.exit(EXIT_SUCCESS)
Capturando la salida con try/except
Cuando llamamos a sys.exit()
, Python internamente lanza una excepción SystemExit
que podemos capturar si es necesario:
import sys
def operacion_peligrosa():
# Simular una operación que podría fallar
valor = input("Ingrese un número (o 'salir' para terminar): ")
if valor.lower() == 'salir':
print("Saliendo por solicitud del usuario...")
sys.exit(0)
try:
numero = int(valor)
return 100 / numero
except ValueError:
print("Error: Debe ingresar un número válido")
sys.exit(1)
except ZeroDivisionError:
print("Error: No se puede dividir por cero")
sys.exit(2)
# Capturar la salida del programa
try:
resultado = operacion_peligrosa()
print(f"Resultado: {resultado}")
except SystemExit as e:
codigo_salida = e.code
print(f"El programa intentó salir con código: {codigo_salida}")
print("Realizando limpieza antes de salir...")
# Aquí podríamos realizar operaciones de limpieza
# Propagar la excepción para terminar realmente
raise
Esta técnica es útil cuando necesitamos realizar operaciones de limpieza antes de que el programa termine, como cerrar conexiones a bases de datos o liberar recursos.
Flujos de salida estándar
El módulo sys
proporciona acceso a los flujos estándar que podemos usar para controlar dónde se envía la salida:
import sys
# Escribir en la salida estándar (stdout)
sys.stdout.write("Este es un mensaje normal\n")
# Escribir en la salida de error (stderr)
sys.stderr.write("Este es un mensaje de error\n")
# Redirigir temporalmente stdout a un archivo
stdout_original = sys.stdout
try:
with open('registro.log', 'w') as archivo_log:
sys.stdout = archivo_log
print("Este mensaje va al archivo de registro")
# Podemos seguir usando stderr para errores
sys.stderr.write("Este error sigue yendo a la consola\n")
finally:
# Restaurar stdout
sys.stdout = stdout_original
print("De vuelta a la consola")
La separación entre stdout
y stderr
es especialmente útil cuando queremos filtrar o redirigir diferentes tipos de salida:
# En la línea de comandos podemos hacer:
# python script.py > salida.txt 2> errores.txt
Flush de la salida
A veces, la salida puede quedar almacenada en un buffer y no mostrarse inmediatamente. Podemos forzar la escritura con flush()
:
import sys
import time
# Simulación de una operación larga con progreso
for i in range(10):
sys.stdout.write(f"Procesando... {i*10}%\r")
sys.stdout.flush() # Forzar la escritura inmediata
time.sleep(0.5)
sys.stdout.write("Procesamiento completado! \n")
En Python 3, también podemos usar el parámetro flush
de la función print()
:
for i in range(10):
print(f"Procesando... {i*10}%", end='\r', flush=True)
time.sleep(0.5)
print("Procesamiento completado! ")
Supresión de la salida
En ocasiones, queremos suprimir temporalmente toda la salida de un programa o función:
import sys
import os
from contextlib import contextmanager
@contextmanager
def suprimir_salida():
"""Contexto que suprime temporalmente stdout y stderr."""
# Guardar los descriptores originales
stdout_original = sys.stdout
stderr_original = sys.stderr
# Redirigir a /dev/null (o NUL en Windows)
devnull = open(os.devnull, 'w')
sys.stdout = devnull
sys.stderr = devnull
try:
yield
finally:
# Restaurar los descriptores originales
sys.stdout = stdout_original
sys.stderr = stderr_original
devnull.close()
# Ejemplo de uso
def funcion_ruidosa():
print("¡Mensaje muy ruidoso!")
print("¡Otro mensaje!")
return 42
# Con salida normal
resultado1 = funcion_ruidosa()
print(f"Resultado 1: {resultado1}")
# Con salida suprimida
with suprimir_salida():
resultado2 = funcion_ruidosa()
print(f"Resultado 2: {resultado2}")
Captura y análisis de la salida
Podemos capturar la salida de una función para analizarla o procesarla:
import sys
from io import StringIO
def capturar_salida(funcion, *args, **kwargs):
"""Ejecuta una función y captura su salida stdout."""
# Guardar stdout original
stdout_original = sys.stdout
# Crear un buffer en memoria para capturar la salida
buffer_salida = StringIO()
sys.stdout = buffer_salida
try:
# Ejecutar la función
resultado = funcion(*args, **kwargs)
# Obtener la salida capturada
salida = buffer_salida.getvalue()
return resultado, salida
finally:
# Restaurar stdout
sys.stdout = stdout_original
# Función de ejemplo que genera salida
def analizar_datos(valores):
total = sum(valores)
promedio = total / len(valores)
print(f"Análisis de {len(valores)} valores:")
print(f"- Total: {total}")
print(f"- Promedio: {promedio:.2f}")
print(f"- Máximo: {max(valores)}")
print(f"- Mínimo: {min(valores)}")
return {
"total": total,
"promedio": promedio,
"maximo": max(valores),
"minimo": min(valores)
}
# Capturar y procesar la salida
datos = [12, 45, 3, 67, 34, 21]
resultado, salida = capturar_salida(analizar_datos, datos)
print("\nResultado de la función:")
print(resultado)
print("\nSalida capturada:")
print(salida)
# Podríamos analizar la salida, por ejemplo:
lineas = salida.strip().split('\n')
print(f"\nNúmero de líneas en la salida: {len(lineas)}")
Ejemplo práctico: Herramienta de línea de comandos
Veamos un ejemplo más completo que integra varias técnicas de control de salida:
import sys
import os
import time
import json
class Procesador:
def __init__(self, archivo_entrada, archivo_salida=None, verbose=False):
self.archivo_entrada = archivo_entrada
self.archivo_salida = archivo_salida
self.verbose = verbose
self.estadisticas = {
"lineas_procesadas": 0,
"errores": 0,
"tiempo_inicio": time.time(),
"tiempo_fin": None
}
def log(self, mensaje, es_error=False):
"""Registra un mensaje según el nivel de verbosidad."""
if es_error:
sys.stderr.write(f"ERROR: {mensaje}\n")
sys.stderr.flush()
elif self.verbose:
sys.stdout.write(f"INFO: {mensaje}\n")
sys.stdout.flush()
def procesar(self):
"""Procesa el archivo de entrada."""
self.log(f"Iniciando procesamiento de '{self.archivo_entrada}'")
try:
if not os.path.exists(self.archivo_entrada):
self.log(f"El archivo '{self.archivo_entrada}' no existe", es_error=True)
return sys.exit(2)
with open(self.archivo_entrada, 'r') as entrada:
# Preparar salida
if self.archivo_salida:
salida = open(self.archivo_salida, 'w')
else:
salida = sys.stdout
try:
# Procesar línea por línea
for i, linea in enumerate(entrada):
try:
# Simular procesamiento (convertir a mayúsculas)
resultado = linea.upper()
# Escribir resultado
salida.write(resultado)
# Actualizar estadísticas
self.estadisticas["lineas_procesadas"] += 1
# Mostrar progreso
if self.verbose and i % 100 == 0:
self.log(f"Procesadas {i+1} líneas...")
except Exception as e:
self.log(f"Error al procesar línea {i+1}: {e}", es_error=True)
self.estadisticas["errores"] += 1
finally:
# Cerrar archivo de salida si no es stdout
if self.archivo_salida:
salida.close()
# Registrar tiempo de finalización
self.estadisticas["tiempo_fin"] = time.time()
duracion = self.estadisticas["tiempo_fin"] - self.estadisticas["tiempo_inicio"]
# Mostrar resumen
self.log(f"Procesamiento completado en {duracion:.2f} segundos")
self.log(f"Líneas procesadas: {self.estadisticas['lineas_procesadas']}")
if self.estadisticas["errores"] > 0:
self.log(f"Se encontraron {self.estadisticas['errores']} errores", es_error=True)
return sys.exit(1)
return sys.exit(0)
except KeyboardInterrupt:
self.log("\nProcesamiento interrumpido por el usuario", es_error=True)
return sys.exit(130) # Código estándar para SIGINT
except Exception as e:
self.log(f"Error fatal: {e}", es_error=True)
return sys.exit(1)
def main():
# Procesar argumentos
if len(sys.argv) < 2:
sys.stderr.write("Uso: python script.py <archivo_entrada> [archivo_salida] [--verbose]\n")
sys.exit(2)
archivo_entrada = sys.argv[1]
archivo_salida = None
verbose = False
# Procesar argumentos opcionales
for arg in sys.argv[2:]:
if arg == "--verbose" or arg == "-v":
verbose = True
elif not arg.startswith("-"):
archivo_salida = arg
# Ejecutar procesador
procesador = Procesador(archivo_entrada, archivo_salida, verbose)
procesador.procesar()
if __name__ == "__main__":
main()
Este script puede usarse de varias formas:
# Procesar un archivo y mostrar resultado en la consola
python procesador.py datos.txt
# Procesar un archivo y guardar resultado en otro archivo
python procesador.py datos.txt resultado.txt
# Procesar con mensajes informativos
python procesador.py datos.txt --verbose
# Redirigir la salida estándar a un archivo
python procesador.py datos.txt > resultado.txt
# Redirigir los errores a un archivo separado
python procesador.py datos.txt 2> errores.log
Mejores prácticas para la salida del programa
Al trabajar con la salida del programa, es recomendable seguir estas prácticas:
- Usa códigos de salida consistentes para indicar diferentes tipos de errores.
- Separa claramente los mensajes informativos (stdout) de los mensajes de error (stderr).
- Proporciona mensajes de error descriptivos que ayuden a diagnosticar problemas.
- Implementa diferentes niveles de verbosidad para controlar la cantidad de información mostrada.
- Considera el entorno donde se ejecutará tu programa (terminal interactiva, script automatizado, etc.).
- Maneja adecuadamente las interrupciones del usuario (como Ctrl+C).
- Realiza limpieza de recursos antes de terminar el programa.
import sys
import signal
import atexit
# Registrar función de limpieza
def limpiar():
print("Realizando limpieza final...")
# Cerrar archivos, conexiones, etc.
atexit.register(limpiar)
# Manejar señales de interrupción
def manejador_interrupcion(signum, frame):
print("\nInterrumpido por el usuario")
# La limpieza se realizará automáticamente por atexit
sys.exit(130)
signal.signal(signal.SIGINT, manejador_interrupcion)
# Ejemplo de programa principal
print("Programa en ejecución. Presiona Ctrl+C para interrumpir.")
try:
# Simulación de trabajo
for i in range(10):
print(f"Trabajando... {i+1}/10")
# Hacer algo útil aquí
import time
time.sleep(1)
print("Trabajo completado con éxito")
sys.exit(0)
except Exception as e:
print(f"Error inesperado: {e}", file=sys.stderr)
sys.exit(1)
El control adecuado de la salida del programa es fundamental para crear aplicaciones robustas y fáciles de usar, especialmente cuando se desarrollan herramientas de línea de comandos o scripts que formarán parte de flujos de trabajo automatizados.
Otros ejercicios de programación de Python
Evalúa tus conocimientos de esta lección Módulo sys con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Módulo math
Reto herencia
Excepciones
Introducción a Python
Reto variables
Funciones Python
Reto funciones
Módulo datetime
Reto acumulación
Reto estructuras condicionales
Polimorfismo
Módulo os
Reto métodos dunder
Diccionarios
Reto clases y objetos
Reto operadores
Operadores
Estructuras de control
Funciones lambda
Reto diccionarios
Reto función lambda
Encapsulación
Reto coleciones
Reto funciones auxiliares
Crear módulos y paquetes
Módulo datetime
Excepciones
Operadores
Diccionarios
Reto map, filter
Reto tuplas
Proyecto gestor de tareas CRUD
Tuplas
Variables
Tipos de datos
Conjuntos
Reto mixins
Módulo csv
Módulo json
Herencia
Análisis de datos de ventas con Pandas
Reto fechas y tiempo
Reto estructuras de iteración
Funciones
Reto comprehensions
Variables
Reto serialización
Módulo csv
Reto polimorfismo
Polimorfismo
Clases y objetos
Reto encapsulación
Estructuras de control
Importar módulos y paquetes
Módulo math
Funciones lambda
Reto excepciones
Listas
Reto archivos
Encapsulación
Reto conjuntos
Clases y objetos
Instalación de Python y creación de proyecto
Reto listas
Tipos de datos
Crear módulos y paquetes
Tuplas
Herencia
Reto acceso a sistema
Proyecto sintaxis calculadora
Importar módulos y paquetes
Clases y objetos
Módulo os
Listas
Conjuntos
Reto tipos de datos
Reto matemáticas
Módulo json
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
Introducción
Instalación Y Creación De Proyecto
Introducción
Tema 2: Tipos De Datos, Variables Y Operadores
Introducción
Instalación De Python
Introducción
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Estructuras Control Iterativo
Sintaxis
Estructuras Control Condicional
Sintaxis
Testing Con Pytest
Sintaxis
Listas
Estructuras De Datos
Tuplas
Estructuras De Datos
Diccionarios
Estructuras De Datos
Conjuntos
Estructuras De Datos
Comprehensions
Estructuras De Datos
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Mixins Y Herencia Múltiple
Programación Orientada A Objetos
Métodos Especiales (Dunder Methods)
Programación Orientada A Objetos
Composición De Clases
Programación Orientada A Objetos
Funciones Lambda
Programación Funcional
Aplicación Parcial
Programación Funcional
Entrada Y Salida, Manejo De Archivos
Programación Funcional
Decoradores
Programación Funcional
Generadores
Programación Funcional
Paradigma Funcional
Programación Funcional
Composición De Funciones
Programación Funcional
Funciones Orden Superior Map Y Filter
Programación Funcional
Funciones Auxiliares
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Archivos Comprimidos
Entrada Y Salida Io
Entrada Y Salida Avanzada
Entrada Y Salida Io
Archivos Temporales
Entrada Y Salida Io
Contexto With
Entrada Y Salida Io
Módulo Csv
Biblioteca Estándar
Módulo Json
Biblioteca Estándar
Módulo Datetime
Biblioteca Estándar
Módulo Math
Biblioteca Estándar
Módulo Os
Biblioteca Estándar
Módulo Re
Biblioteca Estándar
Módulo Random
Biblioteca Estándar
Módulo Time
Biblioteca Estándar
Módulo Collections
Biblioteca Estándar
Módulo Sys
Biblioteca Estándar
Módulo Statistics
Biblioteca Estándar
Módulo Pickle
Biblioteca Estándar
Módulo Pathlib
Biblioteca Estándar
Importar Módulos Y Paquetes
Paquetes Y Módulos
Crear Módulos Y Paquetes
Paquetes Y Módulos
Entornos Virtuales (Virtualenv, Venv)
Entorno Y Dependencias
Gestión De Dependencias (Pip, Requirements.txt)
Entorno Y Dependencias
Python-dotenv Y Variables De Entorno
Entorno Y Dependencias
Acceso A Datos Con Mysql, Pymongo Y Pandas
Acceso A Bases De Datos
Acceso A Mongodb Con Pymongo
Acceso A Bases De Datos
Acceso A Mysql Con Mysql Connector
Acceso A Bases De Datos
Novedades Python 3.13
Características Modernas
Operador Walrus
Características Modernas
Pattern Matching
Características Modernas
Instalación Beautiful Soup
Web Scraping
Sintaxis General De Beautiful Soup
Web Scraping
Tipos De Selectores
Web Scraping
Web Scraping De Html
Web Scraping
Web Scraping Para Ciencia De Datos
Web Scraping
Autenticación Y Acceso A Recursos Protegidos
Web Scraping
Combinación De Selenium Con Beautiful Soup
Web Scraping
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender cómo acceder y procesar argumentos de línea de comandos con sys.argv.
- Conocer las variables y funciones del módulo sys para interactuar con el intérprete y el sistema operativo.
- Aprender a gestionar y modificar las rutas de importación de módulos mediante sys.path.
- Controlar la salida del programa y la terminación con sys.exit(), así como manejar flujos estándar de entrada y salida.
- Aplicar buenas prácticas para el manejo de argumentos, rutas y salida en programas Python.