Обработка системных ошибок через 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.ENOENT → FileNotFoundError).
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)