El módulo errno expone nombres simbólicos para códigos de error del sistema operativo usados por archivos, procesos, sockets, dispositivos y llamadas nativas. En vez de comparar números sin contexto como 2, 13 o 111, el código puede usar ENOENT, EACCES y ECONNREFUSED.
Python moderno convierte muchos códigos en subclases específicas de OSError, como FileNotFoundError, PermissionError, BlockingIOError y ConnectionRefusedError. Aun así, errno sigue siendo importante en integración nativa, I/O no bloqueante y manejo portable de condiciones menos comunes.
Por qué usar nombres simbólicos
Los valores numéricos pueden variar entre sistemas. Un nombre expresa la condición y mejora la portabilidad.
import errno
try:
with open("config.ini", "rb") as archivo:
datos = archivo.read()
except OSError as exc:
if exc.errno == errno.ENOENT:
print("Archivo no encontrado")
else:
raise
Cuando existe una excepción específica, suele ser más clara:
try:
with open("config.ini", "rb") as archivo:
datos = archivo.read()
except FileNotFoundError:
print("Archivo no encontrado")
Usa errno cuando varios códigos comparten clase o una biblioteca nativa devuelve solo el entero.
Atributos de OSError
OSError suele ofrecer errno, strerror, filename y a veces filename2. No todas las instancias tienen datos completos.
try:
origen.replace(destino)
except OSError as exc:
print("código:", exc.errno)
print("mensaje:", exc.strerror)
print("archivo:", exc.filename)
No tomes decisiones con el texto de strerror. Cambia según idioma, plataforma y versión. Compara la clase o el código.
Convertir número en nombre
errno.errorcode mapea valores disponibles a sus nombres.
import errno
codigo = errno.EACCES
nombre = errno.errorcode.get(codigo, "ERROR_DESCONOCIDO")
print(codigo, nombre)
No todos los símbolos existen en todos los sistemas. Usa hasattr() o getattr() para código multiplataforma.
Convertir código en mensaje
os.strerror() devuelve la descripción del sistema.
import errno
import os
print(os.strerror(errno.ENOSPC))
El texto sirve para logs e interfaces, no como contrato estable. Devuelve un identificador interno separado del mensaje localizado.
Errores frecuentes de archivos
Valores comunes: ENOENT para ruta ausente; EACCES o EPERM para acceso denegado; EEXIST para destino existente; ENOTDIR cuando un componente no es directorio; EISDIR cuando se esperaba archivo; ENOSPC para disco lleno; EROFS para almacenamiento de solo lectura; y EXDEV en operaciones entre dispositivos.
Para archivos temporales y reemplazo atómico, consulta tempfile en Python. Incluso un flujo correcto debe manejar permisos y falta de espacio.
EXDEV entre filesystems
Path.rename() y os.rename() pueden fallar con EXDEV cuando origen y destino están en filesystems diferentes. La aplicación puede copiar, verificar y borrar el origen si esa semántica es aceptable.
import errno
import shutil
try:
origen.replace(destino)
except OSError as exc:
if exc.errno != errno.EXDEV:
raise
shutil.copy2(origen, destino)
origen.unlink()
El fallback no conserva exactamente la atomicidad. Añade verificación de integridad y limpieza ante fallos parciales.
Operaciones no bloqueantes
EAGAIN, EWOULDBLOCK, EINPROGRESS y EALREADY aparecen con sockets y descriptores no bloqueantes. Python suele representarlos mediante BlockingIOError.
import errno
try:
datos = sock.recv(4096)
except BlockingIOError as exc:
if exc.errno in {errno.EAGAIN, errno.EWOULDBLOCK}:
datos = None
else:
raise
En algunas plataformas EAGAIN y EWOULDBLOCK comparten valor. No hagas busy loop; espera disponibilidad con selectors o select.
Errores de red
Entre los códigos importantes están ECONNREFUSED, ECONNRESET, ECONNABORTED, ETIMEDOUT, EHOSTUNREACH, ENETUNREACH, EADDRINUSE y EADDRNOTAVAIL.
El guía de socketserver en Python aborda servidores y cierre. Para HTTP, urllib.error en Python separa respuestas HTTP de fallos de transporte.
Broken pipe
EPIPE ocurre al escribir en un pipe o socket cuyo lector cerró. Python normalmente genera BrokenPipeError.
try:
conexion.sendall(payload)
except BrokenPipeError:
cerrar_sesion()
Deja de enviar, elimina el descriptor del loop, libera recursos y registra un diagnóstico limitado.
Llamadas interrumpidas
EINTR indica una llamada interrumpida por señal. Desde PEP 475, varias APIs repiten automáticamente cuando el handler no lanza excepción. Bibliotecas nativas aún pueden exponer InterruptedError.
while True:
try:
return operacion()
except InterruptedError:
continue
Repite solo operaciones seguras. Una llamada puede haber producido efectos parciales.
Integración con ctypes
El artículo anterior, ctypes en Python, usa use_errno=True para mantener una copia thread-local leída mediante get_errno().
import errno
import os
from ctypes import CDLL, get_errno
lib = CDLL("libejemplo.so", use_errno=True)
resultado = lib.abrir_recurso()
if resultado == -1:
codigo = get_errno()
if codigo == errno.EACCES:
raise PermissionError(codigo, os.strerror(codigo))
raise OSError(codigo, os.strerror(codigo))
Lee el código inmediatamente. Otra llamada nativa puede sobrescribirlo.
Diferencias de plataforma
Linux, macOS, BSD, Windows y WASI ofrecen subconjuntos distintos. errno.errorcode refleja el entorno actual.
import errno
codigo_cuota = getattr(errno, "EDQUOT", None)
if codigo_cuota is not None:
print("La cuota de disco puede identificarse")
Al enviar un error a otro servicio, no transmitas solo el entero. Incluye un código de aplicación y, opcionalmente, el símbolo y la plataforma.
Evitar capturas amplias
Convertir cualquier OSError en “archivo inexistente” oculta permisos, disco lleno y fallos de dispositivo.
try:
cargar_configuracion()
except FileNotFoundError:
crear_configuracion_predeterminada()
Captura solo condiciones que sabes resolver. Lo mismo vale para retries: un disco lleno no mejora con repetición inmediata.
Clasificar fallos reintentables
TRANSITORIOS = {
errno.EAGAIN,
errno.EINTR,
errno.ETIMEDOUT,
}
def puede_reintentarse(exc: OSError) -> bool:
return exc.errno in TRANSITORIOS
La decisión depende también de idempotencia. Repetir escrituras, pagos, borrados o POST puede duplicar efectos. Combina códigos, límites de intentos, jitter y backoff exponencial.
Logs estructurados
def error_para_log(exc: OSError) -> dict:
return {
"tipo": type(exc).__name__,
"errno": exc.errno,
"simbolo": errno.errorcode.get(exc.errno),
"mensaje": exc.strerror,
}
Añade la operación y un recurso redactado, no credenciales o rutas sensibles completas.
Pruebas recomendadas
Prueba archivos ausentes, acceso denegado, destinos existentes, directorio usado como archivo, almacenamiento de solo lectura, disco lleno, sockets rechazados, timeout, conexión resetada, I/O no bloqueante y símbolos ausentes.
Los tests unitarios pueden construir OSError con códigos conocidos, pero conserva tests de integración reales porque los mappings cambian entre sistemas.
Errores comunes
Los fallos frecuentes son comparar mensajes localizados, usar números mágicos, asumir que todos los símbolos existen, capturar cualquier OSError, repetir operaciones no idempotentes, leer errno nativo demasiado tarde y exponer códigos del sistema como API pública permanente.
Conclusión
errno aporta nombres significativos a los códigos de fallo del sistema. Complementa las excepciones específicas de Python y sigue siendo valioso en llamadas nativas e I/O no bloqueante.
Prefiere excepciones específicas, compara símbolos en vez de texto y diseña retries considerando el error y la operación. Consulta la documentación oficial de errno y la documentación oficial de OSError.







