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

Влияет ли исключение на производительность в Python?

2.0 Middle🔥 121 комментариев
#Python Core

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

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

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

Влияет ли исключение на производительность в Python

Да, исключения влияют на производительность, и это важно понимать при разработке критичного по производительности кода.

Создание исключения

Создание исключения — дорогостоящая операция:

import time

# Тест 1: создание исключения
start = time.perf_counter()
for _ in range(100000):
    exc = ValueError("Error message")
end = time.perf_counter()
print(f"Create exception: {(end - start) * 1000:.2f}ms")  # ~50-100ms

# Тест 2: обычное значение
start = time.perf_counter()
for _ in range(100000):
    val = "Error message"
end = time.perf_counter()
print(f"Create string: {(end - start) * 1000:.2f}ms")  # ~1-2ms

# Создание исключения медленнее в 50+ раз!

Почему исключения медленные

import traceback
import sys

# Исключение захватывает весь стек вызовов
def level_3():
    raise ValueError("Test")

def level_2():
    level_3()

def level_1():
    level_2()

# Это сохраняет весь стек (expensive!)
try:
    level_1()
except ValueError as e:
    traceback.print_exc()  # Печатает весь стек
    
    # Под капотом:
    # - Сохраняются фреймы для каждого уровня
    # - Сохраняются локальные переменные
    # - Создаётся backtrace
    # - Это требует памяти и времени

Обработка исключения

Обработка (try/except) также дорогостоящая:

import time

# Тест 1: с исключением
def with_exception():
    try:
        return 1 / 0
    except ZeroDivisionError:
        return -1

start = time.perf_counter()
for _ in range(100000):
    with_exception()
end = time.perf_counter()
print(f"With exception: {(end - start) * 1000:.2f}ms")  # ~100-200ms

# Тест 2: без исключения, обычная проверка
def without_exception():
    if True:  # Проверка вместо исключения
        return 1 / 1
    return -1

start = time.perf_counter()
for _ in range(100000):
    without_exception()
end = time.perf_counter()
print(f"Without exception: {(end - start) * 1000:.2f}ms")  # ~1-5ms

# Обработка исключения медленнее в 50+ раз!

Реальные примеры

Плохо: использование исключений для контроля потока

# ❌ МЕДЛЕННО: использование исключений как control flow
def parse_number_bad(value):
    try:
        return int(value)
    except ValueError:
        return None

# Тестирование
import timeit

# Много валидных чисел
valid_numbers = [str(i) for i in range(1000)]
time1 = timeit.timeit(
    lambda: [parse_number_bad(x) for x in valid_numbers],
    number=100
)
print(f"Exception approach (valid): {time1:.3f}s")

# Много невалидных значений
invalid_numbers = ["abc", "xyz", "test"] * 333
time2 = timeit.timeit(
    lambda: [parse_number_bad(x) for x in invalid_numbers],
    number=100
)
print(f"Exception approach (invalid): {time2:.3f}s")

Хорошо: проверка вместо исключения

# ✅ БЫСТРО: проверка строки перед парсингом
def parse_number_good(value):
    # Проверить, является ли строка числом
    if isinstance(value, str) and value.isdigit():
        return int(value)
    return None

time_good = timeit.timeit(
    lambda: [parse_number_good(x) for x in invalid_numbers],
    number=100
)
print(f"Check approach: {time_good:.3f}s")  # Намного быстрее!

Исключение для ошибок (OK)

# ✅ ХОРОШО: исключение для редких ошибок
def divide(a, b):
    if b == 0:
        raise ValueError("Division by zero")
    return a / b

# Нормальная производительность, т.к. исключение редкое
for i in range(1000000):
    divide(10, i + 1)  # Не выбрасывает исключение

Стоимость разных операций

import timeit

# Операция 1: Нормальный return
def normal_return():
    return 42

# Операция 2: Выброс исключения
def raise_exception():
    raise ValueError("Test")

# Операция 3: Try/except без исключения
def try_no_exception():
    try:
        return 42
    except ValueError:
        return -1

# Операция 4: Try/except с исключением
def try_with_exception():
    try:
        raise ValueError("Test")
    except ValueError:
        return -1

print("Timing 1M iterations:")
print(f"Normal return: {timeit.timeit(normal_return, number=1000000):.3f}s")  # ~0.01s
print(f"Try/no exception: {timeit.timeit(try_no_exception, number=1000000):.3f}s")  # ~0.02s
print(f"Raise/catch: {timeit.timeit(try_with_exception, number=1000000):.3f}s")  # ~0.5-1s

Оптимизация с EAFP vs LBYL

Python рекомендует EAFP (Easier to Ask for Forgiveness than Permission) — проще попросить прощение, чем разрешение:

# LBYL (Look Before You Leap) — проверить перед использованием
def dict_access_lbyl(d, key):
    if key in d:
        return d[key]
    return None

# EAFP (Easier to Ask for Forgiveness) — попросить прощения
def dict_access_eafp(d, key):
    try:
        return d[key]
    except KeyError:
        return None

# Для существующих ключей (нормально)
d = {k: v for k, v in zip(range(1000), range(1000))}
key = 500

time_lbyl = timeit.timeit(
    lambda: dict_access_lbyl(d, key),
    number=100000
)
time_eafp = timeit.timeit(
    lambda: dict_access_eafp(d, key),
    number=100000
)
print(f"LBYL: {time_lbyl:.3f}s, EAFP: {time_eafp:.3f}s")  # EAFP быстрее при наличии ключа!

# Для несуществующих ключей (EAFP медленнее)
key = 5000
time_lbyl = timeit.timeit(
    lambda: dict_access_lbyl(d, key),
    number=100000
)
time_eafp = timeit.timeit(
    lambda: dict_access_eafp(d, key),
    number=100000
)
print(f"LBYL: {time_lbyl:.3f}s, EAFP: {time_eafp:.3f}s")  # EAFP медленнее при отсутствии ключа

Когда исключения OK

# ✅ Исключения для редких ошибок (OK по производительности)
class Database:
    def query(self, sql):
        try:
            # Нормальный путь - без исключений
            result = execute_sql(sql)
            return result
        except ConnectionError:
            # Редкая ошибка - исключение OK
            reconnect()
            return execute_sql(sql)
        except SyntaxError:
            # Редкая ошибка - исключение OK
            raise ValueError("Invalid SQL")

# ✅ Исключения в конструкторах (редко вызываются)
class Config:
    def __init__(self, path):
        try:
            with open(path) as f:
                self.data = json.load(f)
        except FileNotFoundError:
            raise ConfigError(f"Config not found: {path}")
        except json.JSONDecodeError:
            raise ConfigError(f"Invalid JSON: {path}")

Когда избегать исключений

# ❌ Исключения для частого контроля потока (медленно)
class Logger:
    def log(self, level, message):
        try:
            # Это выполняется часто!
            if self.should_log(level):
                write_to_file(message)
        except IOError:
            pass  # Плохо: IOError редко, но try/except всегда медленно

# ✅ Вместо этого: проверить перед try
class Logger:
    def log(self, level, message):
        if not self.should_log(level):
            return
        try:
            write_to_file(message)
        except IOError:
            pass  # try/except только для редкого случая

Профилирование исключений

import cProfile
import pstats

def with_exceptions():
    for i in range(1000):
        try:
            1 / (10 - i)
        except ZeroDivisionError:
            pass

def with_checks():
    for i in range(1000):
        if i != 10:
            1 / (10 - i)

# Профилировать
cProfile.run("with_exceptions()", sort="cumtime")
cProfile.run("with_checks()", sort="cumtime")

# Вывод покажет, что exceptions дорогие!

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

# 1. Исключения для РЕДКИХ ошибок
try:
    file_data = open("config.json").read()
except FileNotFoundError:
    file_data = get_default_config()

# 2. Проверка для ЧАСТЫХ условий
if value is None:
    return default_value

# 3. Не использовать исключения для control flow
# ❌ try/except для пустого списка
# ✅ if not my_list: ...

# 4. Кэшировать исключительные состояния
@functools.lru_cache
def is_valid_email(email):
    try:
        validate_email(email)
        return True
    except ValidationError:
        return False

# 5. Минимизировать код в try блоке
try:
    result = expensive_operation()
except ExpensiveError:
    return None
# Вместо:
try:
    result = expensive_operation()
    do_something_else()
    do_another_thing()
except ExpensiveError:
    return None

Итого

Влияние исключений на производительность:

  • Создание исключения медленнее обычного значения в 50+ раз
  • Обработка try/except медленнее обычного кода в 50+ раз
  • Используй исключения для РЕДКИХ ошибок, не для control flow
  • Используй проверки (if) для часто случаемых условий
  • В Python рекомендуется EAFP, но только для редких случаев
  • Профилируй код, чтобы найти узкие места с исключениями