⚖️ Старый и современный подход

⚡ Различия в двух словах

Из источника: простые рекурсивные функции без явной валидации, иногда с type().

Современная практика: guard clauses с isinstance, ранний возврат, конкретные исключения, f-strings, type hints.

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

📜 Как может выглядеть «плоский» подход

Без явной валидации функция может получить неожиданные данные и упасть позже, на внутренней операции. Пример с type() вместо isinstance:

# Naive approach from the source style
def recursive_sum(numbers):
    if type(numbers) != list:  # less flexible than isinstance
        print("Ошибка: аргумент должен быть списком")
        return None
    if not numbers:
        return 0
    return numbers[0] + recursive_sum(numbers[1:])


print(recursive_sum([1, 2, "x", 4]))  # crashes at runtime, no element check

Этот код работает только для списков верхнего уровня, не проверяет элементы и не возбуждает исключение — он просто возвращает None.

❌ Что можно улучшить

  • type(x) == list не поддерживает наследование и выглядит неидиоматично.
  • Вывод ошибки через print и возврат None скрывают причину проблемы от вызывающего кода.
  • Нет проверки элементов: строка или словарь внутри списка сломают рекурсию на позднем этапе.
  • Нет type hints — сложнее понять, какие типы ожидает функция.

✅ Современный вариант

def recursive_sum(numbers: list) -> float:
    """Return the sum of all numbers in a list recursively."""
    if not isinstance(numbers, list):
        raise TypeError("Argument must be a list")
    if not numbers:
        return 0

    head = numbers[0]
    if not isinstance(head, (int, float)):
        raise TypeError(f"All elements must be numbers, got {type(head).__name__}")
    return head + recursive_sum(numbers[1:])


numbers = [1, 2, 3, 4, 5]
try:
    print(recursive_sum(numbers))
except (TypeError, ValueError) as e:
    print(f"Ошибка: {e}")

Что улучшилось:

  • Guard clause с isinstance в начале функции.
  • Конкретное исключение TypeError вместо None или общего Exception.
  • Проверка каждого элемента перед сложением.
  • Type hints делают сигнатуру функции понятной.
  • f-string формирует понятное сообщение об ошибке.

Рекомендуемый стиль обработки ошибок

try:
    print(reverse_string(123))
except TypeError as e:
    print(f"Ошибка: {e}")

Ловим только тот тип исключения, который может возникнуть, и сообщаем пользователю понятный текст.

🕰️ Когда старый подход ещё можно встретить

Учебные и быстрые скрипты часто используют print и простые проверки. Это нормально для демонстрации, но в домашних заданиях и реальных проектах стоит применять isinstance, guard clauses и конкретные исключения.

⚠️ Проверить по документации: сравнение с type() и рекомендации по isinstance выходят за рамки исходного материала; актуальные рекомендации всегда стоит сверять с официальной документацией Python.