Ошибки выполнения системного уровня в Python: разбор и решения

Раздел: Ошибки -> Ошибки выполнения

Понимание системных ошибок Python

Системные ошибки в Python (system error) чаще всего представлены исключением OSError и его подклассами: FileNotFoundError, PermissionError, ProcessLookupError и другими. Они возникают, когда операционная система не может выполнить запрошенную операцию (открытие файла, запуск процесса, сетевое соединение). Каждая такая ошибка содержит атрибут errno (числовой код системной ошибки), strerror (текстовое описание) и часто filename (имя файла/пути).

Универсальный перехват и анализ системной ошибки

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

Самый надёжный способ - использовать конструкцию try/except с перехватом OSError и проверкой кода ошибки через модуль errno. Это позволяет выполнять разные действия для разных типов сбоев (файл не найден, нет прав, занят ресурс).

import os, errno

try:
    with open('data.txt', 'r') as f:
        content = f.read()
except OSError as e:
    if e.errno == errno.ENOENT:
        print(f"Файл {e.filename} не найден")
    elif e.errno == errno.EACCES:
        print(f"Нет доступа к {e.filename}")
    elif e.errno == errno.EBUSY:
        print(f"Ресурс занят: {e.filename}")
    else:
        print(f"Системная ошибка {e.errno}: {e.strerror}")

Name is not defined python (ошибка 'nameerror: name is not defined' в python)

Файл data.txt не найден

Python return error (ошибка возврата в python)

Как не потерять исходное исключение и сохранить traceback?

Используйте raise ... from e для повторного поднятия с сохранением контекста, если ошибку не удалось исправить.

try:
    os.remove('/tmp/lock')
except OSError:
    raise RuntimeError("Не удалось удалить блокировочный файл") from None

Runtime error python (ошибка времени выполнения python)

Проблема: Слишком общий перехват может скрыть непредвиденные ошибки. Решение: Перехватывать только конкретные подклассы или проверять errno. Не оставляйте пустой блок except.

Вариант 1: Предварительная проверка существования файла

Как избежать ошибки FileNotFoundError перед открытием файла?

Используйте os.path.exists() или pathlib.Path.exists(). Это снижает количество исключений, но не защищает от гонок (race condition) – файл может быть удалён между проверкой и открытием. В многопоточных сценариях лучше полагаться на обработку исключений.

from pathlib import Path

path = Path('config.ini')
if path.exists():
    with open(path, 'r') as f:
        data = f.read()
else:
    print("Файл config.ini не существует. Создаю с настройками по умолчанию.")
    path.write_text('[DEFAULT]\nkey=value')

Python exit error (ошибка выхода из python)

Проблема: Между проверкой и открытием может произойти изменение файла. Решение: Комбинировать проверку с try/except на случай, если файл внезапно исчезнет.

Вариант 2: Проверка прав доступа перед операцией

Как избежать ошибки PermissionError при записи в защищённую директорию?

Функция os.access() позволяет проверить наличие прав на чтение/запись. Однако она также подвержена race condition и не учитывает возможности изменения прав в процессе работы.

import os

filename = '/etc/config.toml'
if os.access(filename, os.W_OK):
    with open(filename, 'a') as f:
        f.write('new setting\n')
else:
    print(f"Нет прав на запись в {filename}. Попробуйте sudo.")

System error python (системная ошибка python)

Проблема: os.access() не всегда корректно работает на некоторых файловых системах (NFS). Решение: Использовать try/except с PermissionError и предложить изменить права через os.chmod() или запуск с повышенными привилегиями.

Вариант 3: Обработка ошибок при запуске внешних команд (subprocess)

Как поймать ошибку, если внешняя программа завершилась с ненулевым кодом?

При использовании subprocess.run() с флагом check=True будет поднято исключение CalledProcessError. В блоке except можно получить код возврата, stdout и stderr.

import subprocess

try:
    result = subprocess.run(['ls', '/nonexistent'], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
    print(f"Команда завершилась с кодом {e.returncode}")
    print(f"stdout: {e.stdout}")
    print(f"stderr: {e.stderr}")
Команда завершилась с кодом 2
stdout: 
stderr: ls: cannot access '/nonexistent': No such file or directory

Проблема: Игнорирование ошибок без check=True оставляет код возврата непроверенным. Решение: Всегда проверять returncode или использовать check=True.

Вариант 4: Сетевые ошибки и повторные попытки

Как обработать временный сбой сети при соединении с сервером?

Исключение socket.error (или ConnectionError) можно перехватить и выполнить повторные попытки с экспоненциальной задержкой (backoff).

import socket
import time

host, port = 'example.com', 80
max_retries = 3
for attempt in range(max_retries):
    try:
        with socket.create_connection((host, port), timeout=5) as sock:
            print("Соединение установлено")
            break
    except (socket.timeout, ConnectionRefusedError) as e:
        print(f"Попытка {attempt+1} не удалась: {e}")
        time.sleep(2 ** attempt)  # экспоненциальная задержка
else:
    print("Не удалось подключиться после нескольких попыток")

Проблема: Бесконечные повторные попытки могут заблокировать программу. Решение: Ограничить количество попыток и добавить общий таймаут.

Вариант 5: Игнорирование ожидаемой системной ошибки

Как пропустить ошибку, если она не критична для логики программы?

Используйте contextlib.suppress() или пустой блок except (не рекомендуется без логирования). Удобно для операций, которые могут не выполняться из-за временных причин, например, удаление временного файла.

from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('/tmp/temp_file.txt')

Это эквивалентно try: os.remove(...) except FileNotFoundError: pass.

Проблема: Легко проглотить неожиданные ошибки (например, PermissionError). Решение: Явно указывать, какие исключения подавлять, и логировать их для отладки.

Вариант 6: Кастомная обработка с разбором errno

Как реализовать гибкую логику в зависимости от кода системной ошибки?

Модуль errno содержит константы для всех распространённых кодов (ENOENT, EACCES, EEXIST, EBADF и др.). Пример обработки при создании директории:

import os, errno

try:
    os.mkdir('/tmp/new_dir')
except OSError as e:
    if e.errno == errno.EEXIST:
        print("Директория уже существует")
    elif e.errno == errno.EACCES:
        print("Недостаточно прав для создания")
    else:
        raise  # перебросить непредвиденную ошибку
Директория уже существует

Проблема: Код может быть платформозависимым (разные errno на Windows/Linux). Решение: Использовать подклассы OSError (FileExistsError, PermissionError) вместо числовых кодов, если логика не требует платформенной специфики.

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

Пример 1: Обработка ошибок при копировании файла с разными типами сбоев

Показана работа с shutil.copy2 и перехват различных подклассов OSError.

Пример
import shutil
import os

try:
    shutil.copy2('source.txt', '/etc/target.txt')
except FileNotFoundError as e:
    print(f"Исходный файл {e.filename} не найден")
except PermissionError as e:
    print(f"Нет прав на запись в {e.filename}")
except OSError as e:
    print(f"Системная ошибка {e.errno}: {e.strerror}")
Нет прав на запись в /etc/target.txt

В этом примере явно указаны конкретные подклассы, что улучшает читаемость и контроль.

Пример 2: Использование os.replace с обработкой ошибок замены файла

Операция атомарной замены файла может вызвать FileNotFoundError или PermissionError.

Пример
import os

old_path = 'temp.dat'
new_path = 'result.dat'
try:
    os.replace(old_path, new_path)
except FileNotFoundError:
    print("Файл temp.dat не существует. Замена отменена.")
except PermissionError:
    print("Нет прав на перемещение. Может быть, result.dat заблокирован?")
else:
    print(f"Файл {old_path} успешно заменён на {new_path}")
Файл temp.dat не существует. Замена отменена.

Пример 3: Обработка ошибок при работе с файловой системой через pathlib

Методы Path также выбрасывают OSError. Показана обработка при создании вложенных директорий.

Пример
from pathlib import Path
import errno

try:
    Path('/root/secret/subdir').mkdir(parents=True, exist_ok=True)
except OSError as e:
    if e.errno == errno.EACCES:
        print("Недостаточно прав для создания /root/secret")
    else:
        print(f"Ошибка создания: {e}")
Недостаточно прав для создания /root/secret

Пример 4: Обработка системной ошибки при получении информации о файле (os.stat)

Вызов os.stat на несуществующем файле приводит к FileNotFoundError. Демонстрация с атрибутами исключения.

Пример
import os

try:
    info = os.stat('missing_file')
except FileNotFoundError as e:
    print(f"Объект {e.filename} не найден (код {e.errno})")
except OSError as e:
    print(f"Другая системная ошибка: {e.strerror}")
Объект missing_file не найден (код 2)

Пример 5: Обработка ошибок ввода-вывода при чтении большого файла с повреждёнными блоками

Используется io.BufferedReader и перехват OSError с кодом EIO.

Пример
import io
import errno

try:
    with open('/dev/sdb1', 'rb') as f:
        f.read(1024)
except OSError as e:
    if e.errno == errno.EIO:
        print("Ошибка ввода/вывода: повреждённый сектор или отключение устройства")
    else:
        print(f"Неожиданная ошибка: {e.strerror}")
Ошибка ввода/вывода: повреждённый сектор или отключение устройства

Пример 6: Обработка ошибок при использовании mmap

Отображение файла в память может упасть с PermissionError или InvalidArgumentError (на Windows).

Пример
import mmap
import os

with open('test.dat', 'r+b') as f:
    try:
        mm = mmap.mmap(f.fileno(), 0)
    except (OSError, ValueError) as e:
        print(f"Не удалось создать mmap: {e}")
Не удалось создать mmap: [Errno 13] Permission denied

Ошибка возникает, если файл открыт без разрешения на чтение/запись или размер нулевой.

Пример 7: Использование errno для детальной диагностики в сетевых сокетах

Пример
import socket
import errno

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
    sock.connect(('127.0.0.1', 9999))
except OSError as e:
    if e.errno == errno.ECONNREFUSED:
        print("Соединение отклонено. Сервер не запущен.")
    elif e.errno == errno.ETIMEDOUT:
        print("Таймаут соединения. Проверьте настройки сети.")
    else:
        print(f"Сетевая ошибка {e.errno}: {e.strerror}")
Соединение отклонено. Сервер не запущен.

Пример 8: Обработка ошибок при работе с именованными каналами (FIFO)

Пример
import os

fifo_path = '/tmp/my_fifo'
try:
    os.mkfifo(fifo_path)
except OSError as e:
    if e.errno == 17:  # EEXIST
        print("FIFO уже существует")
    else:
        raise
FIFO уже существует

Обратите внимание: код 17 является платформозависимым, лучше использовать errno.EEXIST.

Системная ошибка Python - comments

En
System error python (python)