Обработка системных ошибок через errno в языке Python

Раздел: Ошибки -> Обработка исключений

Основные подходы к обработке errno

Как определить системную ошибку по числовому коду в исключении OSError?

Надёжный способ перехватить любую системную ошибку и получить её числовой код - использовать except OSError as e и обратиться к атрибуту e.errno. Этот атрибут содержит код errno, определённый операционной системой. Для сравнения кода с константами удобно применять модуль errno, где предопределены все распространённые коды (например, errno.ENOENT, errno.EACCES).

import os, errno

try:
    os.remove("/несуществующий_файл.txt")
except OSError as e:
    if e.errno == errno.ENOENT:
        print(f"Файл не найден (код {e.errno})")
    elif e.errno == errno.EACCES:
        print(f"Нет доступа (код {e.errno})")
    else:
        print(f"Другая ошибка: {e.strerror} (код {e.errno})")

Python ignore error (игнорирование ошибок python)

Файл не найден (код 2)

Python exception (исключения в python)

Цель: точное определение типа системной ошибки, одинаковое на разных платформах (коды errno стандартизированы). Подходит, когда нужно различать десятки возможных кодов в одном блоке except.

Типичная проблема: забывают импортировать модуль errno и сравнивают с числами (например, e.errno == 2). Это делает код непереносимым между разными ОС, так как числовые коды могут различаться. Решение - всегда использовать именованные константы из errno.

Ещё одна сложность: при перехвате общего Exception атрибут .errno может отсутствовать. Поэтому сначала следует убедиться, что исключение содержит этот атрибут, либо перехватывать OSError.

Как упростить обработку известных типов ошибок без явного сравнения errno?

Начиная с Python 3.3, существуют встроенные исключения-наследники OSError: FileNotFoundError, PermissionError, FileExistsError и другие. Они автоматически выбрасываются при соответствующих значениях errno (например, errno.ENOENTFileNotFoundError).

try:
    with open("/несуществующий_файл.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("Файл не найден")
except PermissionError:
    print("Нет прав на чтение")

исключения в python инструкция try except (обработка исключений try except в python)

Файл не найден

Python errno (обработка ошибки errno в python)

Цель: сократить объём кода при обработке самых частых системных ошибок. Используется, когда не требуется отличать редкие коды errno и достаточно «одного уровня» вложенности except.

Типичная ошибка: забывают, что эти исключения являются подклассами OSError, и порядок обработки важен. Если сначала стоит except OSError, то FileNotFoundError никогда не сработает. Решение - располагать более специфичные исключения выше.

Как получить строковое имя кода errno для логирования?

Модуль errno содержит словарь errorcode, где ключи - числовые коды, значения - строковые константы (например, 2'ENOENT'). Это удобно при записи ошибок в лог.

import errno

try:
    os.mkdir("/readonly/test")
except OSError as e:
    err_name = errno.errorcode.get(e.errno, "UNKNOWN")
    print(f"Ошибка {err_name} (код {e.errno}): {e.strerror}")

Python get traceback (получение трассировки стека в python)

Ошибка EACCES (код 13): Permission denied

Цель: повышение читаемости логов, особенно при большом количестве различных кодов. errorcode доступен только для стандартных кодов, но покрывает все распространённые случаи.

Проблема: errorcode не содержит кодов, специфичных для некоторых платформ (например, Linux имеет дополнительные коды вроде EUCLEAN). В таких случаях .get() вернёт 'UNKNOWN'. Решение - дополнительно использовать e.strerror.

Как обрабатывать сетевые ошибки с помощью errno в модуле socket?

Исключение socket.error (которое является синонимом OSError) также содержит атрибут .errno. Это позволяет различать тайм-ауты, отказы в соединении и другие сетевые проблемы.

import socket, errno

try:
    s = socket.socket()
    s.settimeout(1)
    s.connect(("192.0.2.1", 80))
except OSError as e:
    if e.errno == errno.ECONNREFUSED:
        print("Соединение отклонено")
    elif e.errno == errno.ETIMEDOUT:
        print("Тайм-аут соединения")
    elif e.errno == errno.EHOSTUNREACH:
        print("Хост недоступен")
    else:
        print(f"Другая сетевая ошибка: {e}")
Соединение отклонено

Цель: обработка сетевых исключений, где коды errno соответствуют ошибкам POSIX. Полезно при написании серверов, клиентов или библиотек для работы с сетью.

Типичная ошибка: попытка перехватить socket.error как отдельный класс, забыв, что в Python 3 он является псевдонимом OSError. Поэтому можно спокойно использовать except OSError. Однако некоторые старые примеры в интернете используют except socket.error, что всё ещё работает, но менее гибко.

Как сохранить код errno в пользовательских исключениях для согласованности?

При создании собственных классов исключений, которые оборачивают системные вызовы, полезно передавать errno как атрибут, чтобы вышестоящий код мог обработать его единообразно.

import os, errno

class MyFileError(Exception):
    def __init__(self, message, errno_code):
        super().__init__(message)
        self.errno = errno_code

def safe_read(path):
    try:
        with open(path) as f:
            return f.read()
    except OSError as e:
        raise MyFileError(f"Ошибка чтения файла {path}: {e.strerror}", e.errno)

try:
    content = safe_read("/несуществующий_файл.txt")
except MyFileError as e:
    if e.errno == errno.ENOENT:
        print("Файл не найден в пользовательском исключении")
    else:
        print(f"Код ошибки: {e.errno}")
Файл не найден в пользовательском исключении

Цель: инкапсуляция системных ошибок в собственных иерархиях исключений. Позволяет унифицировать интерфейс для разных модулей и добавлять дополнительный контекст.

Проблема: если пользовательское исключение не наследует OSError, то оно не будет автоматически перехватываться блоками except OSError. Необходимо либо наследовать от OSError, либо документировать, что код должен ловить именно ваше исключение.

Расширенные примеры работы с errno

Пример 1. Комбинированная обработка с повторной попыткой

В некоторых ситуациях (например, временные сетевые ошибки) разумно повторять операцию несколько раз, проверяя код errno.

Пример
import os, errno, time

def safe_write_with_retry(filename, data, retries=3):
    for attempt in range(retries):
        try:
            with open(filename, "w") as f:
                f.write(data)
            return True
        except OSError as e:
            if e.errno == errno.ENOSPC:
                print("Нет места на диске, повтор не имеет смысла")
                raise
            elif e.errno in (errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR):
                print(f"Попытка {attempt+1}/{retries} - временная ошибка")
                time.sleep(0.5 * (attempt + 1))
            else:
                raise
    return False

try:
    safe_write_with_retry("/tmp/test.txt", "hello")
except OSError as e:
    print(f"Не удалось записать: {e.errno} {e.strerror}")
(при ошибке ENOSPC выброшено исключение)

Пример 2. Использование errno.errorcode для кастомного логирования

Пример
import errno, logging, os

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

def delete_file_safe(path):
    try:
        os.remove(path)
    except OSError as e:
        err_name = errno.errorcode.get(e.errno, "UNKNOWN")
        logging.error("Ошибка %s при удалении %s: %s", err_name, path, e.strerror)
        raise

delete_file_safe("/несуществующий_файл.txt")
ERROR: Ошибка ENOENT при удалении /несуществующий_файл.txt: No such file or directory

Пример 3. Получение кода errno из исключения сокета при тайм-ауте

Пример
import socket, errno

def check_port(host, port, timeout=2):
    try:
        sock = socket.create_connection((host, port), timeout=timeout)
        sock.close()
        return True
    except OSError as e:
        if e.errno == errno.ETIMEDOUT:
            return False  # порт не открыт за время ожидания
        elif e.errno in (errno.ECONNREFUSED, errno.EHOSTUNREACH):
            return False  # явный отказ
        else:
            raise

print(check_port("192.0.2.1", 80))
False (или исключение при общей ошибке)

Пример 4. Работа с временными файлами и обработка errno

Пример
import tempfile, os, errno

try:
    with tempfile.NamedTemporaryFile(delete=False) as tmp:
        tmp.write(b"data")
        path = tmp.name
    # Имитация ошибки: попытка удалить уже удалённый файл
    os.remove(path)
    os.remove(path)  # второй раз вызовет FileNotFoundError
except FileNotFoundError:
    print("Файл уже был удалён ранее (FileNotFoundError)")
except OSError as e:
    # Если бы FileNotFoundError не сработал, код errno тоже скажет ENOENT
    if e.errno == errno.ENOENT:
        print(f"Файл не найден, errno={e.errno}")
Файл уже был удалён ранее (FileNotFoundError)

Пример 5. Создание обёртки для файловых операций с полным контролем errno

Пример
import errno

class FileManager:
    def __init__(self, path):
        self.path = path
        self.errno_codes = []

    def read(self):
        try:
            with open(self.path) as f:
                return f.read()
        except OSError as e:
            self.errno_codes.append(e.errno)
            raise

try:
    fm = FileManager("/несуществующий_файл.txt")
    print(fm.read())
except OSError:
    print("Записаны коды errno:", fm.errno_codes)
Записаны коды errno: [2]

Пример 6. Комбинирование errno с условной обработкой в зависимости от платформы

Пример
import os, errno, sys

def create_nested_directory(path):
    try:
        os.makedirs(path, exist_ok=True)
    except OSError as e:
        if e.errno == errno.EACCES:
            print("Нет прав на создание каталога")
        elif sys.platform.startswith("win") and e.errno == errno.ENOENT:
            # На Windows ENOENT может означать, что часть пути не существует
            print("Один из компонентов пути не найден")
        else:
            raise

create_nested_directory("/var/restricted/test")
(на Linux может вывести EACCES)

Обработка ошибки errno в Python - comments

En
Python errno (python)