← Назад к вопросам

Зачем нужно перехватывать ошибку в Python?

1.6 Junior🔥 251 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужно перехватывать ошибку в Python

Перехват ошибок (exception handling) в Python нужен для управления непредвиденными ситуациями и обеспечения стабильности приложения. Без перехвата — приложение крахнется.

Проблема без перехвата

# Без перехвата ошибок
def divide(a, b):
    return a / b  # Если b = 0, программа упадёт!

print(divide(10, 2))  # 5.0 — OK
print(divide(10, 0))  # ZeroDivisionError — приложение упало!
print("This line never executes")  # Не выполнится

# Вывод:
# 5.0
# Traceback (most recent call last):
#   File "script.py", line 8, in <module>
#     print(divide(10, 0))
#   File "script.py", line 3, in divide
#     return a / b
# ZeroDivisionError: division by zero

Приложение упало! Пользователь получит 500 ошибку.

Решение: try-except

def divide(a, b):
    try:
        result = a / b  # Рискованный код
    except ZeroDivisionError:
        print("Error: Cannot divide by zero")
        result = None
    return result

print(divide(10, 2))  # 5.0
print(divide(10, 0))  # Error: Cannot divide by zero
print("Program continues...")  # Выполняется!

# Вывод:
# 5.0
# Error: Cannot divide by zero
# Program continues...

Основные типы ошибок

# SyntaxError — ошибка синтаксиса (при парсинге)
if True  # Missing colon

# IndentationError — неправильный отступ
if True:
print('hello')  # Неправильный отступ

# NameError — переменная не определена
print(undefined_variable)

# TypeError — неправильный тип
print("hello" + 5)  # Нельзя складывать string и int

# ValueError — неправильное значение
int("not a number")

# ZeroDivisionError — деление на ноль
print(10 / 0)

# IndexError — индекс вне диапазона
list_var = [1, 2, 3]
print(list_var[10])

# KeyError — ключ не в словаре
dict_var = {'a': 1}
print(dict_var['b'])

# FileNotFoundError — файл не найден
with open('/nonexistent/file.txt') as f:
    pass

# AttributeError — атрибут не существует
class MyClass:
    pass

obj = MyClass()
print(obj.undefined_attr)

try-except-else-finally

def read_file(filename):
    try:
        # Код, который может вызвать ошибку
        with open(filename) as f:
            content = f.read()
    
    except FileNotFoundError:
        # Обработка конкретной ошибки
        print(f"File {filename} not found")
        content = None
    
    except IOError as e:
        # Захват ошибки в переменную
        print(f"IO Error: {e}")
        content = None
    
    except Exception as e:
        # Ловля всех остальных ошибок (bad practice!)
        print(f"Unexpected error: {e}")
        content = None
    
    else:
        # Выполняется если НЕ было ошибки
        print(f"Successfully read {len(content)} characters")
    
    finally:
        # Выполняется ВСЕГДА (cleanup)
        print("File operation completed")
    
    return content

result = read_file('data.txt')

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

Пример 1: API запрос с перехватом

import requests
from requests.exceptions import RequestException, Timeout, ConnectionError

def fetch_user_data(user_id):
    try:
        response = requests.get(
            f'https://api.example.com/users/{user_id}',
            timeout=5
        )
        response.raise_for_status()  # Throws HTTPError for bad status
        return response.json()
    
    except Timeout:
        print(f"Request timeout for user {user_id}")
        return None
    
    except ConnectionError:
        print("Network connection failed")
        return None
    
    except requests.HTTPError as e:
        if e.response.status_code == 404:
            print(f"User {user_id} not found")
        else:
            print(f"HTTP Error: {e}")
        return None
    
    except Exception as e:
        print(f"Unexpected error: {e}")
        return None

user = fetch_user_data(123)
if user:
    print(f"User name: {user['name']}")
else:
    print("Could not fetch user")

Пример 2: Валидация и логирование

import logging

logger = logging.getLogger(__name__)

def process_user_input(user_input):
    try:
        # Конвертируем строку в число
        age = int(user_input)
        
        if age < 0 or age > 150:
            raise ValueError(f"Invalid age: {age}")
        
        return age
    
    except ValueError as e:
        # Логируем ошибку
        logger.error(f"Invalid input: {user_input}. Error: {e}")
        # Возвращаем default значение
        return None
    
    except Exception as e:
        logger.exception(f"Unexpected error processing input: {user_input}")
        return None

age = process_user_input("25")
if age is not None:
    print(f"User age: {age}")

Пример 3: Context Manager (with statement)

В Python можно автоматизировать cleanup с with:

# Без перехвата (может упасть)
def read_file_bad(filename):
    f = open(filename)
    content = f.read()
    f.close()  # Может не выполниться если произойдёт ошибка!
    return content

# С перехватом
def read_file_good(filename):
    try:
        with open(filename) as f:  # Автоматический close!
            return f.read()
    except FileNotFoundError:
        return ""

# Или создать свой context manager
class Database:
    def __init__(self, connection_string):
        self.conn = None
        self.connection_string = connection_string
    
    def __enter__(self):
        try:
            self.conn = connect(self.connection_string)
            return self.conn
        except ConnectionError as e:
            print(f"Failed to connect: {e}")
            raise
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        # Выполняется ВСЕГДА
        if self.conn:
            self.conn.close()
        
        # Можно перехватить ошибки
        if exc_type is not None:
            print(f"Error in database context: {exc_val}")
        
        return False  # Пробросить исключение дальше

# Использование
try:
    with Database('postgresql://localhost/mydb') as db:
        result = db.query('SELECT * FROM users')
except ConnectionError:
    print("Could not connect to database")

Пример 4: Retry logic

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    """
    Декоратор для повторного выполнения функции при ошибке
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt < max_attempts - 1:
                        print(f"Attempt {attempt + 1} failed: {e}. Retrying...")
                        time.sleep(delay)
                    else:
                        print(f"All {max_attempts} attempts failed")
                        raise
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def unstable_api_call():
    # Функция иногда падает
    import random
    if random.random() < 0.7:
        raise ConnectionError("API unavailable")
    return "Success!"

result = unstable_api_call()  # Повторит 3 раза если нужно
print(result)

Пример 5: Цепочка обработки ошибок

class CustomError(Exception):
    """Кастомная ошибка"""
    pass

def process_data(data):
    try:
        if not data:
            raise ValueError("Data is empty")
        
        if len(data) < 10:
            raise CustomError("Data too short")
        
        # Обработка
        result = data.upper()
        return result
    
    except ValueError as e:
        print(f"Validation error: {e}")
        return None
    
    except CustomError as e:
        print(f"Custom error: {e}")
        return data  # Return original
    
    except Exception as e:
        print(f"Unexpected error: {e}")
        raise  # Пробросить дальше

print(process_data(""))  # ValueError
print(process_data("short"))  # CustomError
print(process_data("long enough data"))  # Success

Лучшие практики

Делай так:

try:
    risky_operation()
except SpecificError as e:
    # Обработай конкретную ошибку
    handle_error(e)
finally:
    cleanup()  # Cleanup всегда

Не делай так:

try:
    risky_operation()
except:  # Ловит ВСЕ ошибки (включая KeyboardInterrupt!)
    pass  # Игнорируем ошибку

try:
    risky_operation()
except Exception:  # Очень общее
    pass  # Скрываем проблему

Иерархия ошибок

BaseException
├── SystemExit
├── KeyboardInterrupt  (Ctrl+C)
├── GeneratorExit
└── Exception (Почти всё)
    ├── ValueError
    ├── TypeError
    ├── KeyError
    ├── IndexError
    ├── FileNotFoundError
    ├── requests.RequestException
    └── ... 100+ других

Вывод

Перехват ошибок нужен для:

  1. Стабильности — приложение не падает
  2. Graceful degradation — fallback поведение
  3. Логирования — понимаем что пошло не так
  4. Очистки ресурсов — finally/with
  5. Retry logic — повторная попытка

Без перехвата ошибок приложение будет нестабильным и сложным в отладке. Всегда используй try-except для обработки предсказуемых ошибок.

Зачем нужно перехватывать ошибку в Python? | PrepBro