Python & AI Tutorials Logo
Программирование Python

24. Понимание ошибок и трассировок

Ошибки — неизбежная часть программирования. Каждый программист, от новичка до эксперта, регулярно с ними сталкивается. Разница между тем, чтобы мучиться с ошибками, и тем, чтобы учиться на них, заключается в понимании того, что Python пытается вам сказать, когда что-то идёт не так.

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

В этой главе мы рассмотрим две основные категории ошибок, с которыми вы столкнётесь: синтаксические ошибки (проблемы с тем, как вы написали код) и исключения времени выполнения (проблемы, возникающие во время выполнения кода). Мы научимся читать traceback — подробные отчёты Python об ошибках — и поймём, как исключения меняют обычный ход выполнения вашей программы. И самое главное — мы сформируем мышление отладки, которое воспринимает ошибки не как провалы, а как ценную информацию, помогающую писать более качественный код.

24.1) Синтаксические ошибки vs исключения времени выполнения

Python различает два принципиально разных типа проблем в вашем коде: синтаксические ошибки и исключения времени выполнения. Понимание этого различия помогает быстрее диагностировать проблемы и понимать, где искать решения.

24.1.1) Что такое синтаксические ошибки

Синтаксическая ошибка возникает, когда Python не может понять ваш код, потому что он нарушает грамматические правила языка. Точно так же, как фраза «The cat sat on the» является неполным английским предложением, код с синтаксическими ошибками — это неполный или некорректно сформированный Python-код, который интерпретатор не может разобрать.

Синтаксические ошибки обнаруживаются до запуска вашей программы. Python сначала читает весь ваш скрипт целиком, проверяя, соответствует ли он правилам языка. Если он находит синтаксическую ошибку, он отказывается выполнять любой код — даже те части, которые написаны правильно.

Вот простой пример:

python
# ПРЕДУПРЕЖДЕНИЕ: Синтаксическая ошибка — только для демонстрации
# ОШИБКА: Пропущено двоеточие после оператора if
age = 25
if age >= 18
    print("You are an adult")

Когда вы пытаетесь запустить этот код, Python сразу сообщает:

  File "example.py", line 3
    if age >= 18
                ^
SyntaxError: expected ':'

Обратите внимание на несколько ключевых особенностей этого сообщения об ошибке:

  1. Файл и номер строки: Python точно сообщает, где он нашёл проблему (line 3)
  2. Визуальный указатель: каретка (^) указывает на место, где Python «запутался»
  3. Тип ошибки: SyntaxError ясно показывает, что это грамматическая проблема
  4. Полезная подсказка: expected ':' сообщает, чего не хватает

Код никогда не выполняется, потому что Python не может даже начать его исполнять — синтаксис некорректен.

Рассмотрим ещё одну распространённую синтаксическую ошибку:

python
# ПРЕДУПРЕЖДЕНИЕ: Синтаксическая ошибка — только для демонстрации
# ОШИБКА: Несоответствие скобок
numbers = [1, 2, 3, 4, 5]
total = sum(numbers
print(f"Total: {total}")

Python сообщает:

  File "example.py", line 2
    total = sum(numbers
               ^
SyntaxError: '(' was never closed

Здесь Python обнаружил, что мы открыли круглую скобку на строке 2, но так и не закрыли её. Ошибка сообщается на строке 2 (где находится незакрытая скобка), а каретка указывает на место, где Python ожидал найти закрывающую скобку.

Ключевые характеристики синтаксических ошибок:

  • Обнаруживаются до выполнения любого кода
  • Не дают выполниться всей программе целиком
  • Обычно указывают на опечатки, пропущенную пунктуацию или неправильные отступы
  • Место ошибки может быть немного дальше фактической ошибки

24.1.2) Что такое исключения времени выполнения

Исключение времени выполнения (или просто «исключение») возникает, когда синтаксически корректный код сталкивается с проблемой во время исполнения. Код грамматически правильный с точки зрения Python, но что-то идёт не так, когда программа реально запускается.

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

Вот простой пример:

python
# У этого кода корректный синтаксис, но он вызовет исключение
numbers = [10, 20, 30]
print(numbers[0])  # Output: 10
print(numbers[5])  # Эта строка вызовет IndexError
print("This line never executes")

Output:

10
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(numbers[5])
          ~~~~~~~^^^
IndexError: list index out of range

Обратите внимание, что произошло:

  1. Первый оператор print выполнился успешно (мы увидели 10)
  2. Второй print попытался обратиться к индексу 5, которого не существует
  3. Python выбросил исключение IndexError
  4. Программа остановилась, и третий print так и не выполнился

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

Вот ещё один пример, показывающий другой тип исключения времени выполнения:

python
# Корректный синтаксис, но деление на ноль во время выполнения
def calculate_average(total, count):
    return total / count
 
# Эти вызовы работают нормально
print(calculate_average(100, 4))  # Output: 25.0
print(calculate_average(75, 3))   # Output: 25.0
 
# Этот вызов выбрасывает исключение
print(calculate_average(50, 0))   # ZeroDivisionError

Output:

25.0
25.0
Traceback (most recent call last):
  File "example.py", line 8, in <module>
    print(calculate_average(50, 0))
          ^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 2, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

Функция отлично отработала два раза, но в третьем вызове мы передали 0 в качестве count, что привело к делению на ноль. Python не мог обнаружить эту проблему до тех пор, пока код не запустился с этими конкретными значениями.

Ключевые характеристики исключений времени выполнения:

  • Возникают во время выполнения программы
  • Код синтаксически корректен
  • Часто зависят от конкретных данных или условий
  • Программа выполняется до точки, в которой произошло исключение
  • Разные входные данные могут приводить к разным исключениям (или не приводить ни к каким)

24.1.3) Сравнение синтаксических ошибок и исключений времени выполнения

Давайте посмотрим на оба типа ошибок рядом, чтобы понять различия:

python
# Пример 1: синтаксическая ошибка
# ОШИБКА: Пропущена закрывающая кавычка
print("Program started!")
message = "Hello, world
print(message)

Это сразу выдаёт синтаксическую ошибку:

  File "example.py", line 4
    message = "Hello, world
              ^
SyntaxError: unterminated string literal (detected at line 4)

Важно: Обратите внимание, что вы не видите «Program started!» в выводе. Python обнаружил синтаксическую ошибку до запуска любого кода.

Теперь сравним с исключением времени выполнения:

python
# Пример 2: исключение времени выполнения
# Корректный синтаксис, но переменная не существует
print("Program started!")
message = "Hello, world"
print(mesage)  # Опечатка: 'mesage' вместо 'message'

Output:

Program started!
Traceback (most recent call last):
  File "example.py", line 5, in <module>
    print(mesage)
          ^^^^^^
NameError: name 'mesage' is not defined

Важно: На этот раз вы видите «Program started!» в выводе. Python успешно выполнил первые два print и присваивание (строки 3–4), но столкнулся с проблемой на строке 5 при попытке найти mesage.

Ключевое различие: в первом примере Python даже не пытался выполнить код — он обнаружил синтаксическую ошибку во время разбора. Во втором примере Python успешно начал выполнять программу и выполнил несколько строк, прежде чем столкнуться с ошибкой времени выполнения.

Нет

Да

Нет

Да

Python читает ваш код

Синтаксис корректен?

Синтаксическая ошибка
Программа не запускается

Программа начинает выполняться

Проблема во время
выполнения?

Программа завершается
успешно

Исключение времени выполнения
Программа останавливается

24.2) Распространённые встроенные типы исключений

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

24.2.1) NameError: использование неопределённых имён

NameError возникает, когда вы пытаетесь использовать переменную, функцию или другое имя, которое Python не распознаёт. Обычно это означает, что вы забыли что-то определить, ошиблись в написании имени или пытаетесь использовать что-то до того, как оно создано.

python
# Пример 1: забыли определить переменную
print(greeting)  # NameError: name 'greeting' is not defined

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    print(greeting)
          ^^^^^^^^
NameError: name 'greeting' is not defined

Python говорит вам, что он не знает, что такое greeting. Сначала нужно создать эту переменную:

python
# Правильная версия
greeting = "Hello, Python!"
print(greeting)  # Output: Hello, Python!

Вот более тонкий пример с опечаткой:

python
# Пример 2: опечатка в имени переменной
user_name = "Alice"
age = 30
 
print(f"{username} is {age} years old")  # NameError: name 'username' is not defined

Мы определили user_name (с подчёркиванием), но попытались использовать username (без подчёркивания). Python воспринимает их как совершенно разные имена.

24.2.2) TypeError: неверный тип для операции

TypeError возникает, когда вы пытаетесь выполнить операцию над значением неподходящего типа. Например, нельзя сложить строку и целое число или вызвать как функцию то, что функцией не является.

python
# Пример 1: смешивание несовместимых типов
age = 25
message = "You are " + age + " years old"  # TypeError

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    message = "You are " + age + " years old"
              ~~~~~~~~~~~~^~~~~
TypeError: can only concatenate str (not "int") to str

Python говорит вам, что оператор + может конкатенировать строки со строками, но не строки с целыми числами. Нужно преобразовать целое число в строку:

python
# Правильная версия
age = 25
message = "You are " + str(age) + " years old"
print(message)  # Output: You are 25 years old

TypeError также возникает, когда вы передаёте в функцию неправильное количество аргументов:

python
# Пример 3: неправильное количество аргументов
def calculate_area(length, width):
    return length * width
 
area = calculate_area(5)  # TypeError: missing 1 required positional argument

Output:

Traceback (most recent call last):
  File "example.py", line 4, in <module>
    area = calculate_area(5)
TypeError: calculate_area() missing 1 required positional argument: 'width'

Функция ожидает два аргумента, но мы передали только один.

24.2.3) ValueError: правильный тип, неправильное значение

ValueError возникает, когда вы передаёте значение правильного типа, но само значение не подходит для операции. Тип верный, но конкретное значение не имеет смысла в данном контексте.

python
# Пример 1: преобразование некорректной строки в целое число
user_input = "twenty-five"
age = int(user_input)  # ValueError: invalid literal for int()

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    age = int(user_input)
ValueError: invalid literal for int() with base 10: 'twenty-five'

Функция int() ожидает строку, и мы дали ей строку — то есть тип корректен. Но строку "twenty-five" нельзя преобразовать в целое число, потому что она содержит буквы. Строка "25" работала бы нормально:

python
# Правильная версия
user_input = "25"
age = int(user_input)
print(age)  # Output: 25

ValueError также возникает при использовании методов списка:

python
# Пример 3: удаление несуществующего элемента
fruits = ["apple", "banana", "orange"]
fruits.remove("grape")  # ValueError: 'grape' is not in list

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    fruits.remove("grape")
    ~~~~~~~~~~~~~^^^^^^^^^
ValueError: list.remove(x): x not in list

Метод remove() ожидает значение, которое существует в списке. Сначала стоит проверить:

python
# Правильная версия
fruits = ["apple", "banana", "orange"]
if "grape" in fruits:
    fruits.remove("grape")
else:
    print("Grape not found in list")  # Output: Grape not found in list

24.2.4) IndexError: недопустимый индекс последовательности

IndexError возникает, когда вы пытаетесь обратиться к последовательности (списку, кортежу, строке) по индексу, которого не существует. Помните, что в Python индексация начинается с нуля, и допустимые индексы лежат в диапазоне от 0 до len(sequence) - 1.

python
# Пример 1: индекс слишком большой
colors = ["red", "green", "blue"]
print(colors[0])  # Output: red
print(colors[3])  # IndexError: list index out of range

Output:

red
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(colors[3])
          ~~~~~~^^^
IndexError: list index out of range

В списке три элемента с индексами 0, 1 и 2. Индекс 3 не существует. Это очень распространённая ошибка, когда вы забываете, что индексация начинается с 0:

python
# Правильная версия
colors = ["red", "green", "blue"]
print(colors[2])  # Output: blue (the third element)

24.2.5) KeyError: отсутствующий ключ словаря

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

python
# Пример 1: доступ к несуществующему ключу
student = {
    "name": "Alice",
    "age": 20,
    "major": "Computer Science"
}
 
print(student["name"])   # Output: Alice
print(student["grade"])  # KeyError: 'grade'

Output:

Alice
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    print(student["grade"])
          ~~~~~~~^^^^^^^^^
KeyError: 'grade'

В словаре нет ключа "grade". Можно сначала проверить, существует ли ключ:

python
# Правильная версия с использованием 'in'
student = {
    "name": "Alice",
    "age": 20,
    "major": "Computer Science"
}
 
if "grade" in student:
    print(student["grade"])
else:
    print("Grade not available")  # Output: Grade not available

Или использовать метод get(), который возвращает None (или значение по умолчанию) вместо выбрасывания ошибки:

python
# Альтернатива с использованием get()
grade = student.get("grade")
if grade is not None:
    print(f"Grade: {grade}")
else:
    print("Grade not available")  # Output: Grade not available

KeyError часто возникает при обработке данных с непоследовательной структурой:

python
# Пример 2: обработка нескольких записей
students = [
    {"name": "Alice", "age": 20, "grade": "A"},
    {"name": "Bob", "age": 21},  # Отсутствует ключ 'grade'
    {"name": "Carol", "age": 19, "grade": "B"}
]
 
for student in students:
    print(f"{student['name']}: {student['grade']}")  # KeyError на Bob

Output:

Alice: A
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    print(f"{student['name']}: {student['grade']}")
                                ~~~~~~~^^^^^^^^^
KeyError: 'grade'

Используйте get() со значением по умолчанию, чтобы корректно обрабатывать отсутствие ключей:

python
# Правильная версия
students = [
    {"name": "Alice", "age": 20, "grade": "A"},
    {"name": "Bob", "age": 21},
    {"name": "Carol", "age": 19, "grade": "B"}
]
 
for student in students:
    grade = student.get("grade", "Not assigned")
    print(f"{student['name']}: {grade}")

Output:

Alice: A
Bob: Not assigned
Carol: B

24.2.6) AttributeError: неверный доступ к атрибуту

AttributeError возникает, когда вы пытаетесь обратиться к атрибуту или методу, которого нет у объекта. Это часто случается, когда путают методы разных типов или делают опечатки в имени атрибута.

python
# Пример 1: неправильный метод для типа
numbers = [1, 2, 3, 4, 5]
numbers.append(6)  # Это работает — у списков есть append()
print(numbers)     # Output: [1, 2, 3, 4, 5, 6]
 
text = "Hello"
text.append("!")   # AttributeError: 'str' object has no attribute 'append'

Output:

[1, 2, 3, 4, 5, 6]
Traceback (most recent call last):
  File "example.py", line 6, in <module>
    text.append("!")
    ^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'append'

У строк нет метода append(), потому что они неизменяемые. Нужно использовать конкатенацию или другие строковые методы:

python
# Правильная версия
text = "Hello"
text = text + "!"  # Конкатенация
print(text)        # Output: Hello!

AttributeError также возникает из-за опечаток:

python
# Пример 2: опечатка в имени метода
message = "Python Programming"
result = message.uppper()  # AttributeError: 'str' object has no attribute 'uppper'

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = message.uppper()
             ^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'uppper'. Did you mean: 'upper'?

Обратите внимание: Python 3.10+ часто предлагает правильное написание! Правильный метод — upper():

python
# Правильная версия
message = "Python Programming"
result = message.upper()
print(result)  # Output: PYTHON PROGRAMMING

24.2.7) ZeroDivisionError: деление на ноль

ZeroDivisionError возникает, когда вы пытаетесь разделить число на ноль, что математически не определено. Это часто происходит из-за пользовательского ввода или вычисленных значений, которые вы не ожидали увидеть равными нулю.

python
# Пример 1: прямое деление на ноль
result = 10 / 0  # ZeroDivisionError: division by zero

Output:

Traceback (most recent call last):
  File "example.py", line 1, in <module>
    result = 10 / 0
             ~~~^~~
ZeroDivisionError: division by zero

Это также относится к целочисленному делению и операции остатка:

python
# Пример 2: другие операции деления
a = 10 // 0  # ZeroDivisionError
b = 10 % 0   # ZeroDivisionError

Более реалистичный пример связан с вычислениями:

python
# Пример 3: вычисление среднего
def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
scores = [85, 90, 78, 92]
print(calculate_average(scores))  # Output: 86.25
 
empty_scores = []
print(calculate_average(empty_scores))  # ZeroDivisionError

Output:

86.25
Traceback (most recent call last):
  File "example.py", line 9, in <module>
    print(calculate_average(empty_scores))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

У пустого списка длина равна 0, что приводит к делению на ноль. Всегда проверяйте это условие:

python
# Правильная версия
def calculate_average(numbers):
    if len(numbers) == 0:
        return 0  # Или вернуть None, или выбросить более описательное исключение
    
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
scores = [85, 90, 78, 92]
print(calculate_average(scores))  # Output: 86.25
 
empty_scores = []
print(calculate_average(empty_scores))  # Output: 0

Распространённые типы исключений

NameError
Неопределённая переменная/функция

TypeError
Неверный тип для операции

ValueError
Тип верный, значение неверное

IndexError
Недопустимый индекс последовательности

KeyError
Отсутствующий ключ словаря

AttributeError
Недопустимый атрибут/метод

ZeroDivisionError
Деление на ноль

Понимание этих распространённых типов исключений помогает быстро диагностировать проблемы. Когда вы видите исключение, имя типа сразу сообщает, к какой категории относится проблема, а сообщение об ошибке даёт конкретные детали о том, что именно пошло не так.

24.3) Подробное чтение и интерпретация traceback

Когда происходит исключение времени выполнения, Python не просто сообщает, что пошло не так — он предоставляет подробный traceback, показывающий, как именно ваша программа дошла до этой точки. Умение читать traceback необходимо для эффективной отладки. Traceback похож на «след из хлебных крошек», показывающий путь, который прошла ваша программа перед тем, как столкнуться с ошибкой.

24.3.1) Анатомия traceback

Начнём с простого примера и разберём каждую часть traceback:

python
# Простая программа с ошибкой
def calculate_discount(price, discount_percent):
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
def process_order(item_price, discount):
    discounted_price = calculate_discount(item_price, discount)
    tax = discounted_price * 0.08
    total = discounted_price + tax
    return total
 
# Основная программа
original_price = "50"  # Упс! Здесь должно быть число
discount_rate = 10
final_cost = process_order(original_price, discount_rate)
print(f"Final cost: ${final_cost:.2f}")

Output:

Traceback (most recent call last):
  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: can't multiply sequence by non-int of type 'float'

Давайте разберём каждый компонент этой трассировки:

1. Заголовок: "Traceback (most recent call last):"

Эта строка сообщает, что далее идёт traceback — запись вызовов функций. Фраза «most recent call last» означает, что traceback показан в хронологическом порядке: самая ранняя функция в цепочке показана первой, а место, где ошибка действительно произошла, показано последним.

2. Стек вызовов (читаем сверху вниз):

  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Это первый вызов функции в цепочке. Он показывает:

  • Имя файла: "example.py" — где расположен код
  • Номер строки: line 16 — точная строка, из которой сделан вызов
  • Контекст: in <module> — этот код находится на верхнем уровне (не внутри функции)
  • Код: фактическая строка, которая выполнялась
  • Подсветка: символы ^ указывают на конкретную часть строки, связанную с ошибкой

Контекст <module> означает, что это код, выполняющийся на уровне модуля (основная часть вашего скрипта), а не внутри какой-либо функции.

  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Это второй вызов функции. Функция process_order была вызвана из строки 16, и теперь мы находимся внутри этой функции на строке 8, где она вызывает calculate_discount.

  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~

Здесь ошибка действительно произошла. Мы находимся внутри функции calculate_discount на строке 2, и именно эта строка вызвала проблему.

3. Сообщение об ошибке:

TypeError: can't multiply sequence by non-int of type 'float'

Это фактическая ошибка, которая произошла:

  • Тип исключения: TypeError — сообщает категорию ошибки
  • Описание: остальная часть конкретно объясняет, что пошло не так

В данном случае Python сообщает, что мы попытались умножить последовательность (в данном случае — строку) на float, что недопустимо.

24.3.2) Чтение traceback снизу вверх

Хотя traceback печатается в хронологическом порядке (сверху вниз), опытные программисты часто читают его снизу вверх, потому что фактическая ошибка находится внизу, а строки выше показывают, как мы к ней пришли.

Давайте прочитаем нашу предыдущую трассировку снизу вверх:

Шаг 1: начните с сообщения об ошибке

TypeError: can't multiply sequence by non-int of type 'float'

«Хорошо, мы попытались умножить последовательность на float. Так нельзя».

Шаг 2: посмотрите, где произошла ошибка

  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~

«Ошибка произошла в функции calculate_discount на строке 2. Мы пытаемся умножить price на что-то».

Шаг 3: проследите, как мы сюда попали

  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)

«Функция calculate_discount была вызвана из process_order на строке 8, при этом item_price передан в параметр price».

Шаг 4: продолжайте подниматься выше

  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)

«А process_order был вызван из основной программы на строке 16, передав original_price как item_price».

Шаг 5: найдите первопричину

Теперь можно проследить проблему: original_price — это "50" (строка), она передаётся как item_price в process_order, затем как price в calculate_discount, где мы пытаемся умножить её на float. Решение — сделать original_price числом:

python
# Исправленная версия
def calculate_discount(price, discount_percent):
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
def process_order(item_price, discount):
    discounted_price = calculate_discount(item_price, discount)
    tax = discounted_price * 0.08
    total = discounted_price + tax
    return total
 
# Основная программа — исправили тип
original_price = 50  # Теперь это число, а не строка
discount_rate = 10
final_cost = process_order(original_price, discount_rate)
print(f"Final cost: ${final_cost:.2f}")  # Output: Final cost: $48.60

Чтение traceback

1. Прочитайте сообщение об ошибке
Нижняя строка
2. Найдите место ошибки
Последний блок кода
3. Проследите стек вызовов
Двигайтесь вверх
4. Сформулируйте гипотезу
Что пошло не так?
5. Проверьте и исправьте
Протестируйте решение

Понимание того, как читать traceback, превращает их из пугающих «стен текста» в полезные инструменты отладки. Каждая строка даёт ценную информацию о пути выполнения вашей программы, и с практикой вы сможете быстро находить и исправлять проблемы, следуя подсказкам traceback.

24.4) Как исключения меняют нормальный ход выполнения программы

Когда происходит исключение, оно не просто останавливает вашу программу — оно принципиально меняет то, как программа выполняется. Понимание этого поведения критически важно для написания надёжного кода и для понимания того, что происходит при ошибках.

24.4.1) Нормальный ход выполнения vs ход при исключении

При обычном выполнении Python запускает ваш код строка за строкой, сверху вниз:

python
# Нормальный ход выполнения программы
print("Step 1: Starting calculation")
result = 10 + 5
print(f"Step 2: Result is {result}")
final = result * 2
print(f"Step 3: Final value is {final}")
print("Step 4: Program complete")

Output:

Step 1: Starting calculation
Step 2: Result is 15
Step 3: Final value is 30
Step 4: Program complete

Каждая строка выполняется по порядку. Теперь посмотрим, что происходит, когда возникает исключение:

python
# Ход выполнения программы с исключением
print("Step 1: Starting calculation")
result = 10 / 0  # Это выбрасывает ZeroDivisionError
print(f"Step 2: Result is {result}")  # Это никогда не выполняется
final = result * 2  # Это никогда не выполняется
print(f"Step 3: Final value is {final}")  # Это никогда не выполняется
print("Step 4: Program complete")  # Это никогда не выполняется

Output:

Step 1: Starting calculation
Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = 10 / 0
             ~~~^~~
ZeroDivisionError: division by zero

Обратите внимание, что выполнился только первый оператор print. Как только на строке 2 произошло исключение, Python прекратил выполнение остального кода. Исключение прервало обычный ход выполнения.

24.4.2) Исключения распространяются вверх по стеку вызовов

Когда исключение возникает внутри функции, Python не останавливается на этой функции — он распространяет (propagates) его вверх по стеку вызовов до тех пор, пока кто-то не обработает его или программа не завершится.

python
# Пример 1: распространение исключения через функции
def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    return total / count  # Может вызвать ZeroDivisionError
 
def process_scores(score_list):
    print("Processing scores...")
    avg = calculate_average(score_list)
    print(f"Average calculated: {avg}")
    return avg
 
def main():
    print("Program starting")
    scores = []  # Пустой список
    result = process_scores(scores)
    print(f"Final result: {result}")
    print("Program ending")
 
main()

Output:

Program starting
Processing scores...
Traceback (most recent call last):
  File "example.py", line 18, in <module>
    main()
  File "example.py", line 14, in main
    result = process_scores(scores)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 9, in process_scores
    avg = calculate_average(score_list)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

Проследим, что произошло:

  1. main() начал выполняться и вывел "Program starting"
  2. main() вызвал process_scores()
  3. process_scores() вывел "Processing scores..."
  4. process_scores() вызвал calculate_average()
  5. calculate_average() попытался разделить на ноль
  6. Произошло исключение, и оно распространилось вверх:
    • calculate_average() мгновенно остановился (не вернул значение)
    • Управление вернулось в process_scores(), но не «нормально» — исключение продолжало распространяться
    • process_scores() мгновенно остановился (print после calculate_average() так и не выполнился)
    • Управление вернулось в main(), но снова исключение продолжало распространяться
    • main() мгновенно остановился (print после process_scores() так и не выполнился)
  7. Программа завершилась с traceback

Ни одна строка кода после исключения не выполнилась ни в одной из функций. Исключение «всплыло» вверх через все вызовы функций, пока не достигло верхнего уровня и не завершило программу.

24.5) Мышление отладки: воспринимать ошибки как информацию, а не провалы

Один из самых важных навыков в программировании — не написание идеального кода, а умение эффективно работать с неидеальным. Каждый программист, независимо от уровня опыта, пишет код, который приводит к ошибкам. Разница между программистами, которым тяжело, и теми, кто работает эффективно, заключается не в том, чтобы избегать ошибок, а в том, как они на них реагируют.

24.5.1) Ошибки — это не провалы

Когда вы учитесь программировать, естественно испытывать раздражение при столкновении с ошибками. Вам может казаться, что вы сделали что-то не так или что вы «не понимаете». Такой подход контрпродуктивен и, что важнее, неверен.

Ошибки — это не провалы; это обратная связь.

Представьте ошибки как GPS, который пересчитывает маршрут. Когда вы пропускаете поворот, GPS не говорит «Ты провалился!». Он говорит «Пересчитываю маршрут» и даёт новые указания. Сообщения об ошибках Python работают так же: они сообщают, что выбранный путь не сработал, и дают информацию, чтобы помочь вам найти путь, который сработает.

Рассмотрим простой пример:

python
# Первая попытка вычислить среднее
def calculate_average(numbers):
    total = sum(numbers)
    average = total / len(numbers)
    return average
 
scores = []
result = calculate_average(scores)
print(f"Average: {result}")

Output:

Traceback (most recent call last):
  File "example.py", line 8, in <module>
    result = calculate_average(scores)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    average = total / len(numbers)
              ~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero

Эта ошибка не говорит вам, что вы плохой программист. Она говорит вам что-то конкретное и полезное: «Вы попытались разделить на ноль, что происходит, когда список пуст. Вам нужно обработать этот случай».

Имея эту информацию, вы можете улучшить свой код:

python
# Улучшенная версия на основе обратной связи от ошибки
def calculate_average(numbers):
    if len(numbers) == 0:
        return 0  # Or return None, or raise a more descriptive error
    
    total = sum(numbers)
    average = total / len(numbers)
    return average
 
scores = []
result = calculate_average(scores)
print(f"Average: {result}")  # Output: Average: 0

Ошибка помогла вам написать более качественный код. Без неё вы могли бы не понять, что ваша функция не умеет работать с пустыми списками.

24.5.2) Каждая ошибка чему-то учит

Каждая ошибка, с которой вы сталкиваетесь, чему-то учит вас о Python, о вашем коде или о программировании в целом. Давайте рассмотрим несколько примеров того, чему нас учат разные ошибки:

Пример 1: изучаем типы

python
# Попытка сложить несовместимые типы
age = 25
message = "You are " + age + " years old"

Output:

TypeError: can only concatenate str (not "int") to str

Чему это учит: в Python строгие правила типов. Нельзя смешивать строки и числа при конкатенации. Эта ошибка учит вас совместимости типов и знакомит с понятием преобразования типов.

Пример 2: изучаем структуры данных

python
# Попытка обращаться к словарю как к списку
student = {"name": "Alice", "age": 20}
first_value = student[0]

Output:

KeyError: 0

Чему это учит: словари используют ключи, а не числовые индексы. Эта ошибка учит вас разнице между словарями и списками и тому, как правильно получать значения из словаря.

Пример 3: изучаем области видимости

python
# Попытка использовать переменную до её определения
def greet():
    print(f"Hello, {name}!")
 
greet()
name = "Alice"

Output:

NameError: name 'name' is not defined

Чему это учит: переменные должны быть определены до использования, и порядок выполнения имеет значение. Эта ошибка учит вас области видимости переменных и важности инициализации.

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

24.5.3) Принятие мышления отладки

Профессиональные программисты значительную часть времени занимаются отладкой. Это не признак слабости — это ключевая часть работы. Лучшие программисты — не те, кто никогда не ошибается; это те, кто:

  1. Ожидают ошибки: они знают, что ошибки будут, и не удивляются и не разочаровываются
  2. Внимательно читают ошибки: они извлекают максимум информации из сообщений об ошибках
  3. Отлаживают системно: они следуют логическому процессу, а не вносят случайные изменения
  4. Учятся на ошибках: они используют каждую ошибку как возможность лучше понять Python
  5. Сохраняют любопытство: они спрашивают «Почему это произошло?», а не только «Как это исправить?»

Нет

Да

Нет

Да

Столкнулись с ошибкой

Внимательно прочитайте
сообщение об ошибке

Поймите,
что пошло не так

Сформулируйте гипотезу
о причине

Проверьте гипотезу
отладочным выводом

Гипотеза
верна?

Внесите исправление

Протестируйте решение

Работает
правильно?

Извлеките урок
из опыта

Двигайтесь дальше
уверенно

Помните: каждая ошибка — это возможность узнать что-то новое о Python, программировании или решении задач. Воспринимайте ошибки как ценную обратную связь, подходите к ним системно и отмечайте свои успехи в отладке. Это мышление будет полезно на протяжении всего вашего пути в программировании.


Понимание ошибок и traceback — фундамент для того, чтобы стать эффективным Python-программистом. В этой главе мы научились различать синтаксические ошибки (проблемы со структурой кода) и исключения времени выполнения (проблемы во время исполнения), распознавать распространённые типы исключений и то, что они означают, читать и интерпретировать подробные traceback, чтобы находить первопричину проблем, понимать, как исключения меняют ход выполнения программы, распространяясь вверх по стеку вызовов, и развивать мышление отладки, которое воспринимает ошибки как ценную информацию, а не как провалы.

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

© 2025. Primesoft Co., Ltd.
support@primesoft.ai