💻 Практические примеры

⚡ Исключения и файловая система в одной программе

import os
path = "data.txt"
try:
    with open(path, encoding="utf-8") as file:
        print(file.read().strip())
except FileNotFoundError:
    print(f"Файл не найден: {os.path.abspath(path)}")
# файл есть: line one / файла нет: Файл не найден: C:\...\data.txt
КонструкцияЧто делает
try/except/else/finallyперехват ошибки · код при успехе · код в любом случае
raise ValueError("msg")возбудить исключение вручную
os.path.exists/join/abspathпроверить путь · собрать путь · получить абсолютный путь
os.walk(path)рекурсивный обход папок и файлов
sys.argvаргументы командной строки (argv[0] — имя скрипта)
Топ-3 ошибки: «голый» except: вместо конкретного типа · Exception раньше конкретных except в цепочке · склейка путей строкой вместо os.path.join.

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

Пример 1. Базовый try/except

Код в try может упасть с ошибкой; если тип ошибки совпадает с указанным в except, программа не падает, а выполняет обработчик и продолжает работу дальше.

# example_01_basic.py
try:
    result = 10 / 0
    print("Эта строка не выполнится")
except ZeroDivisionError:
    print("Ошибка: деление на ноль!")

print("Программа продолжает работу")
# Ошибка: деление на ноль!
# Программа продолжает работу

Что происходит: исключение возникает на строке 10 / 0, поэтому print("Эта строка не выполнится") не выполняется — управление сразу уходит в подходящий except.

Пример 2. Конкретное исключение vs «голый» except

Три варианта одной и той же ошибки показывают, почему «голый» except: — плохая практика: он скрывает тип и текст ошибки, а заодно перехватывает системные сигналы вроде KeyboardInterrupt.

# example_02a_bare_except.py — так делать нельзя
try:
    number = int("abc")
except:
    print("Что-то пошло не так")

print("Программа продолжает работу, но причина ошибки скрыта")
# Что-то пошло не так
# Программа продолжает работу, но причина ошибки скрыта
# example_02b_broad_exception.py — лучше, но всё ещё расплывчато
try:
    number = int("abc")
except Exception as e:
    print(f"Ошибка: {type(e).__name__}: {e}")
# Ошибка: ValueError: invalid literal for int() with base 10: 'abc'
# example_02c_specific.py — правильно
try:
    number = int("abc")
except ValueError as e:
    print(f"Некорректное значение: {e}")
# Некорректное значение: invalid literal for int() with base 10: 'abc'

Что происходит: все три варианта перехватывают одну и ту же ошибку, но только конкретный except ValueError точно говорит читателю кода, какая именно ошибка ожидается. «Голый» except: перехватит вообще всё, включая ошибки, которые вы не планировали обрабатывать (например, опечатку в другой строке того же блока), и молча спрячет их под общим «Что-то пошло не так».

Почему это топ-ошибка: «голый» except и общий except Exception ловят абсолютно любую ошибку в блоке try — в том числе ту, которую вы не предвидели. Программа не упадёт, но и не покажет, что именно сломалось; отлаживать такой код приходится вслепую.

Пример 3. Несколько except: порядок проверки важен

Обработчики проверяются сверху вниз, и срабатывает первый подходящий. Поэтому более специфичные исключения (ZeroDivisionError, TypeError) ставят раньше общего Exception — иначе общий обработчик перехватит их первым, и специфичный код никогда не выполнится.

# example_03_order.py
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("Деление на ноль")
    except TypeError:
        print("Неверный тип операндов")
    except Exception as e:
        print(f"Другая ошибка: {type(e).__name__}: {e}")


print(safe_divide(10, 0))
print(safe_divide(10, "2"))
print(safe_divide(10, 2))
# Деление на ноль
# None
# Неверный тип операндов
# None
# 5.0

Что происходит: для safe_divide(10, 0) сработал except ZeroDivisionError, для safe_divide(10, "2") — except TypeError (нельзя делить int на str), а safe_divide(10, 2) прошёл без ошибок и вернул 5.0. Функция без return в обработчике неявно возвращает None — отсюда два None в выводе.

Пример 4. Один except на несколько типов через кортеж

Если для двух разных ошибок нужна одна и та же обработка, их типы перечисляют в кортеже: except (ValueError, TypeError) as e. Это короче, чем дублировать одинаковый код в двух блоках except.

# example_04_tuple_except.py
def to_int(value):
    try:
        return int(value)
    except (ValueError, TypeError) as e:
        print(f"Не удалось преобразовать {value!r}: {type(e).__name__}: {e}")
        return None


print(to_int("42"))
print(to_int("abc"))
print(to_int(None))
# 42
# Не удалось преобразовать 'abc': ValueError: invalid literal for int() with base 10: 'abc'
# None
# Не удалось преобразовать None: TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
# None

Что происходит: int("abc") вызывает ValueError (строка не похожа на число), а int(None) — TypeError (значение вообще не строка и не число). Один обработчик except (ValueError, TypeError) ловит оба случая одинаковым образом.

Пример 5. Полная конструкция try/except/else/finally

else выполняется только если в try не было исключения; finally — всегда, независимо от исхода. Пример прогонялся трижды с разным вводом.

# example_05_full.py
try:
    number = int(input("Введите число: "))
    result = 10 / number
except ValueError:
    print("Некорректное значение.")
except ZeroDivisionError:
    print("Деление на ноль.")
else:
    print(f"Результат: {result}")
finally:
    print("Программа завершена.")

# ввод "abc": Некорректное значение.       / Программа завершена.
# ввод "0":   Деление на ноль.             / Программа завершена.
# ввод "5":   Результат: 2.0               / Программа завершена.

Что происходит: для "5" исключения не было, поэтому выполнился else с результатом деления; для "abc" и "0" сработали соответствующие except. В любом случае finally напечатал «Программа завершена.» последним.

Пример 6. raise и повторный запрос ввода

raise позволяет вручную сообщить об ошибке там, где Python сам её не увидит — например, отрицательное число синтаксически корректно, но не подходит по смыслу программы. В цикле это удобно сочетать с повторным запросом ввода.

# example_06_raise.py
while True:
    try:
        number = int(input("Введите положительное число: "))
        if number < 0:
            raise ValueError("Число не может быть отрицательным")
        print(f"Принято: {number}")
        break
    except ValueError as e:
        print(f"Ошибка: {e}. Попробуйте снова.")

# ввод "-3", затем "5":
# Ошибка: Число не может быть отрицательным. Попробуйте снова.
# Принято: 5

Что происходит: при вводе -3 проверка if number < 0 сама возбуждает ValueError, который тут же перехватывает except в этом же блоке — цикл повторяется. При вводе 5 условие не срабатывает, и break завершает цикл.

Пример 7. Логирование ошибок в файл

Вместо print ошибки удобнее записывать через logging — сообщения сохраняются в файл с меткой времени, уровнем и местом возникновения, и не теряются после закрытия консоли.

# example_07_logging.py
import logging

logging.basicConfig(
    filename="app.log",
    format="%(asctime)s - %(filename)s - %(lineno)d - %(levelname)s - %(message)s",
    level=logging.ERROR,
)

try:
    result = 10 / 0
except ZeroDivisionError as e:
    logging.error(f"Ошибка при делении: {e}")
    print("Произошла ошибка, подробности в app.log")
# Произошла ошибка, подробности в app.log

# содержимое app.log:
# 2026-08-13 21:07:59,591 - example_07_logging.py - 12 - ERROR - Ошибка при делении: division by zero

Что происходит: logging.error дописал строку в app.log с реальным временем запуска, именем файла, номером строки и текстом ошибки — этот файл можно смотреть после того, как программа уже закрылась.

Пример 8. os.path: проверка и сборка путей, os.makedirs

os.path даёт функции, которые работают одинаково на Windows и Linux — не нужно вручную думать про / или \. os.makedirs(path, exist_ok=True) создаёт сразу все вложенные папки и не падает, если часть из них уже существует.

# example_08_paths.py
import os

filename = "example.txt"
print("Существует:", os.path.exists(filename))
print("Это файл:", os.path.isfile(filename))
print("Абсолютный путь:", os.path.abspath(filename))
print("Имя файла:", os.path.basename("/home/user/docs/file.txt"))
print("Директория:", os.path.dirname("/home/user/docs/file.txt"))
print("Соединённый путь:", os.path.join("folder", "subfolder", "file.txt"))

os.makedirs("archive/2026/reports", exist_ok=True)
print("Папка создана:", os.path.isdir("archive/2026/reports"))
os.makedirs("archive/2026/reports", exist_ok=True)  # повторный вызов — без ошибки
print("Повторный вызов не упал")
# Существует: True
# Это файл: True
# Абсолютный путь: C:\...\examples-51\example.txt
# Имя файла: file.txt
# Директория: /home/user/docs
# Соединённый путь: folder\subfolder\file.txt
# Папка создана: True
# Повторный вызов не упал

Что происходит: запуск был на Windows, поэтому os.path.join и os.path.abspath вернули путь с обратными слэшами — на Linux/macOS те же вызовы вернули бы прямые слэши, и код при этом не изменился бы ни на строку.

Пример 9. Абсолютный и относительный путь

os.getcwd() возвращает текущую рабочую директорию — точку отсчёта для всех относительных путей. os.path.abspath превращает относительный путь в абсолютный, добавляя её спереди.

# example_09_abs_vs_rel.py
import os

print("Текущая директория (os.getcwd):", os.getcwd())

relative = "project/notes.txt"
absolute = os.path.abspath(relative)
print("Относительный путь:", relative)
print("Абсолютный путь:   ", absolute)
print("Это один и тот же файл:", os.path.exists(relative) == os.path.exists(absolute))
# Текущая директория (os.getcwd): C:\...\examples-51
# Относительный путь: project/notes.txt
# Абсолютный путь:    C:\...\examples-51\project\notes.txt
# Это один и тот же файл: True

Что происходит: project/notes.txt и полный путь до него ссылаются на один и тот же файл — os.path.exists подтвердил это для обоих вариантов. Относительный путь короче и переносимее между машинами, но зависит от того, откуда запущен скрипт; абсолютный путь однозначен, но привязан к конкретному диску и структуре папок.

Пример 10. Рекурсивный обход: os.walk

os.walk обходит дерево директорий и на каждом шаге отдаёт текущую папку, вложенные папки и файлы в ней. Тестовая структура: project/notes.txt, project/report.txt, project/broken.txt и project/subdir/log.txt.

# example_10_walk.py
import os

extension = ".txt"
for root, dirs, files in os.walk("project"):
    for file in files:
        if file.endswith(extension):
            full_path = os.path.join(root, file)
            print(f"Найден файл: {full_path}")
# Найден файл: project\broken.txt
# Найден файл: project\notes.txt
# Найден файл: project\report.txt
# Найден файл: project\subdir\log.txt

Что происходит: os.walk сам спустился во вложенную папку subdir и нашёл файл там же, что и в корне project — рекурсию писать вручную не нужно. Файл broken.txt тоже найден: os.walk отбирает файлы только по имени, ему не важно, что внутри.

Пример 11. Связка тем: безопасный обход папки с обработкой ошибок чтения

Это та самая связка двух тем урока, которую формулирует цель на странице обзора: project/broken.txt — настоящий бинарный файл с некорректными для UTF-8 байтами. Без try/except попытка прочитать его как текст уронила бы всю программу на первом же «плохом» файле.

# example_11_walk_try.py
import os

report_lines = 0
for root, dirs, files in os.walk("project"):
    for file in files:
        if not file.endswith(".txt"):
            continue
        full_path = os.path.join(root, file)
        try:
            with open(full_path, "r", encoding="utf-8") as f:
                content = f.read()
        except UnicodeDecodeError as e:
            print(f"Пропускаю {full_path}: не удалось прочитать как текст ({e})")
            continue
        except OSError as e:
            print(f"Пропускаю {full_path}: ошибка доступа ({e})")
            continue
        report_lines += len(content.splitlines())
        print(f"Прочитан {full_path}: {len(content.splitlines())} стр.")

print(f"Всего успешно прочитано строк: {report_lines}")
# Пропускаю project\broken.txt: не удалось прочитать как текст ('utf-8' codec can't decode byte 0xff in position 0: invalid start byte)
# Прочитан project\notes.txt: 1 стр.
# Прочитан project\report.txt: 1 стр.
# Прочитан project\subdir\log.txt: 1 стр.
# Всего успешно прочитано строк: 3

Что происходит: os.walk находит все четыре файла, но open(..., encoding="utf-8") не может декодировать broken.txt и выбрасывает UnicodeDecodeError. Обработчик перехватывает именно её, печатает понятное сообщение, пропускает файл через continue — и цикл продолжает работу с оставшимися тремя файлами вместо того, чтобы упасть целиком.

⚠️ Проверить по документации: дополнительный except OSError (например, для файлов без прав на чтение) — расширение сверх исходной лекции; в конкретном тестовом прогоне он не сработал ни разу, но защищает от ошибок доступа к файловой системе, которые UnicodeDecodeError не покрывает. Менеджер контекста with тоже не разбирается в исходной лекции, но остаётся рекомендуемым способом гарантировать закрытие файла.

Пример 12. Аргументы командной строки: sys.argv

sys.argv — список строк, которые передали при запуске скрипта из терминала. Первый элемент, sys.argv[0], — всегда имя самого скрипта.

# example_12_argv.py
import sys

print("Все аргументы:", sys.argv)

if len(sys.argv) != 2:
    print("Использование: python example_12_argv.py <имя_файла>")
    sys.exit(1)

target = sys.argv[1]
print(f"Ищем файл: {target}")

# запуск "python example_12_argv.py" (без аргументов):
# Все аргументы: ['example_12_argv.py']
# Использование: python example_12_argv.py <имя_файла>

# запуск "python example_12_argv.py data.txt":
# Все аргументы: ['example_12_argv.py', 'data.txt']
# Ищем файл: data.txt

Что происходит: без аргументов список sys.argv содержит только имя скрипта, проверка len(sys.argv) != 2 срабатывает, и sys.exit(1) завершает программу с ненулевым кодом — это сигнал операционной системе, что что-то пошло не так. С аргументом data.txt список содержит два элемента, и программа идёт дальше.

Как запустить в VS Code

  1. Создайте файл, например summary_51_examples.py.
  2. Скопируйте код одного из примеров.
  3. Откройте терминал в VS Code (Ctrl + `).
  4. Активируйте виртуальное окружение: venv\Scripts\activate (Windows) или source venv/bin/activate (Mac/Linux).
  5. Запустите: python summary_51_examples.py
  6. Для примеров 8–11 предварительно создайте файлы example.txt, data.txt и папку project/ рядом со скриптом; для примера 12 передайте аргумент после имени файла.

Что делать дальше

Если тема исключений подзабылась — вернитесь к уроку 48 «Исключения: обработка ошибок». Если нужна файловая система подробнее — к уроку 50 «Работа с файловой системой». Дальше по программе — урок 52 «File basics». Если что-то из примеров не запустилось — сначала загляните в типичные ошибки.