Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Почему не стоит писать try-except с pass
Паттерн "try-except с pass" — одна из самых опасных ошибок в Python. Это скрывает ошибки и затрудняет отладку.
Проблема 1: Скрытие ошибок
# ПЛОХО: ошибка незаметна, но её не было
try:
result = risky_operation()
except Exception:
pass
print(result) # NameError: name 'result' is not defined
Это не даёт никакой информации о том, что произошло. Ошибка может быть:
- ConnectionError при подключении к БД
- ValueError при парсинге данных
- TypeError при неправильных аргументах
- KeyError при доступе к несуществующему ключу
Вы не узнаете, где проблема, пока код не упадёт гораздо позже.
Проблема 2: Невозможно отладить
# Реальный пример: API запрос
def fetch_user_data(user_id: int):
try:
response = requests.get(f"https://api.example.com/users/{user_id}", timeout=5)
return response.json()
except Exception:
pass # Что произошло? Connection timeout? Invalid JSON? Not found?
user_data = fetch_user_data(123) # None
print(user_data["name"]) # TypeError: 'NoneType' object is not subscriptable
Ошибка выплывет через несколько слоёв кода, и вы не знаете, где она началась.
Проблема 3: Маскирует баги в логике
# ОЧЕНЬ ПЛОХО: баг скрывается
def process_user(user_id):
try:
user = User.objects.get(id=user_id)
user.last_login = datetime.now() # Баг: забыли timezone
user.save()
return user
except Exception:
pass # Баг в datetime скрывается!
user = process_user(1)
if user: # Может быть None, может быть объект
print(user.name)
Проблема 4: Усложняет отладку в продакшене
В продакшене вы часто видите:
# ПЛОХО: лог не поможет понять, что произошло
try:
complex_calculation()
except Exception:
pass # Продукция упала, но нет информации
В логах ничего, на мониторинге появляется, что функция вернула None, и дальше всё ломается. Месячный поиск ошибки.
Правильные подходы
1. Логирование исключения
import logging
logger = logging.getLogger(__name__)
def fetch_user_data(user_id: int):
try:
response = requests.get(f"https://api.example.com/users/{user_id}")
response.raise_for_status() # Выбросить исключение при статусе 4xx/5xx
return response.json()
except requests.RequestException as e:
# Конкретное исключение + логирование
logger.error(f"Failed to fetch user {user_id}: {e}")
raise # Пробросить дальше обработчику
2. Обработка конкретных исключений
def parse_config(config_file: str) -> dict:
try:
with open(config_file, "r") as f:
return json.load(f)
except FileNotFoundError:
logger.error(f"Config file not found: {config_file}")
return get_default_config() # Fallback
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON in config: {e}")
return get_default_config() # Fallback
except IOError as e:
logger.error(f"Can't read config: {e}")
raise # Критическая ошибка, пробросить
3. Возврат значения по умолчанию
def get_optional_data(key: str, default=None):
try:
return cache.get(key)
except CacheError:
logger.warning(f"Cache error for key {key}, using default")
return default
4. Context Manager для управления ресурсами
from contextlib import contextmanager
@contextmanager
def database_connection(dsn: str):
conn = None
try:
conn = psycopg2.connect(dsn)
yield conn
except psycopg2.Error as e:
logger.error(f"Database error: {e}")
if conn:
conn.rollback()
raise
finally:
if conn:
conn.close()
# Использование
with database_connection(DSN) as conn:
conn.execute("SELECT * FROM users")
5. Retry логика вместо молчания
from functools import wraps
import time
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:
logger.error(f"Failed after {max_attempts} attempts: {e}")
raise
logger.warning(f"Attempt {attempt + 1} failed, retrying in {delay}s")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=1)
def unstable_operation():
return requests.get("https://unstable-api.example.com")
6. Использование finally для очистки
def process_file(filename: str):
file_handle = None
try:
file_handle = open(filename, "r")
return parse_content(file_handle.read())
except FileNotFoundError:
logger.error(f"File not found: {filename}")
return None
except ValueError as e:
logger.error(f"Invalid file format: {e}")
return None
finally:
# Гарантирует закрытие файла в любом случае
if file_handle:
file_handle.close()
# Или используй with для автоматического управления
with open(filename, "r") as f:
return parse_content(f.read())
Правило трёх вопросов
Перед написанием try-except спросите себя:
-
Какие конкретные исключения я жду?
- Не пишите except Exception
- Пишите except (ValueError, KeyError):
-
Что я хочу сделать, если это произойдёт?
- Залогировать? ✅
- Повторить операцию? ✅
- Вернуть значение по умолчанию? ✅
- Проигнорировать? ❌ (редко правильный выбор)
-
Должна ли ошибка пройти выше?
- Логирую и пробрасываю (raise)
- Или обрабатываю здесь
# ПРАВИЛЬНО: конкретное исключение + действие
try:
value = int(user_input)
except ValueError:
logger.error(f"Invalid integer input: {user_input}")
value = DEFAULT_VALUE
Пример: трансформация плохого кода
# ПЛОХО: try-except с pass
def load_user(user_id):
try:
user = User.objects.get(id=user_id)
return user
except:
pass
# ХОРОШО: конкретное исключение + логирование
def load_user(user_id: int) -> Optional[User]:
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
logger.debug(f"User {user_id} not found")
return None
except Exception as e:
logger.error(f"Unexpected error loading user {user_id}: {e}")
raise # Критическая ошибка
Инструменты для поиска плохого кода
# flake8 найдёт try-except с pass
flake8 --select=E722 myfile.py
# pylint предупредит
pylint myfile.py
# bare-except: Catching all exceptions (broad-except)
# ruff тоже проверяет
ruff check --select E722 myfile.py
Резюме
Никогда не пишите:
try:
something()
except:
pass
Вместо этого:
- Логируйте исключение с деталями
- Обрабатывайте конкретные исключения
- Пробрасывайте дальше, если это критическая ошибка
- Используйте retry логику для нестабильных операций
- Примите, что исключения — это информация, а не проблема
Хорошее правило: если вы пишете try-except, вы должны уметь объяснить, почему вы ловите именно это исключение и что делаете с информацией о нём.