Отладка Python: обнаружение и устранение ошибок в программах

Раздел: Python -> Отладка Python

Поиск и исправление ошибок в коде Python

Как эффективно локализовать ошибку в Python-скрипте?

Наиболее действенный способ - внимательное чтение трассировки стека (traceback), которую выводит интерпретатор при аварийном завершении программы. Трассировка содержит последовательность вызовов функций, которая привела к ошибке, тип исключения и сообщение. Для детального анализа используется встроенный отладчик pdb.

def divide(a, b):
    return a / b

print(divide(10, 0))

укажите на ошибку python (указание на ошибку в коде python)

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    print(divide(10, 0))
  File "test.py", line 2, in divide
    return a / b
ZeroDivisionError: division by zero

Трассировка указывает точное местоположение ошибки: файл, номер строки, вызов функции и тип исключения. При запуске с отладчиком pdb можно установить точки останова и пошагово пройти выполнение:

import pdb

def divide(a, b):
    pdb.set_trace()  # точка останова
    return a / b

divide(10, 2)

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

Как использовать print-отладку без изменения кода?

Иногда проще вставить временные print-выражения для отслеживания значений переменных. Такой подход не требует дополнительных инструментов и работает в любой среде.

def factorial(n):
    result = 1
    for i in range(1, n + 1):
        print(f"i={i}, result before={result}")  # отладочный вывод
        result *= i
    return result

print(factorial(5))
i=1, result before=1
i=2, result before=1
i=3, result before=2
i=4, result before=6
i=5, result before=24
120

Недостаток - после отладки print-строки нужно удалять, иначе они захламляют вывод. Альтернатива - использовать модуль logging, который позволяет гибко управлять выводом сообщений (уровень, фильтр, вывод в файл).

Как обнаружить ошибку на этапе разработки с помощью assert?

Встроенная инструкция assert позволяет проверить предположения о состоянии программы. Если условие ложно, выбрасывается исключение AssertionError с указанным сообщением. Это помогает рано выявлять логические ошибки.

def sqrt_positive(x):
    assert x >= 0, f"Число {x} должно быть неотрицательным"
    return x ** 0.5

print(sqrt_positive(4))   # 2.0
print(sqrt_positive(-1))  # ошибка
Traceback (most recent call last):
  File "test.py", line 5, in <module>
    print(sqrt_positive(-1))
  File "test.py", line 2, in sqrt_positive
    assert x >= 0, f"Число {x} должно быть неотрицательным"
AssertionError: Число -1 должно быть неотрицательным

assert можно отключить глобальным флагом интерпретатора -O (optimize). Поэтому не следует полагаться на assert для критических проверок в production-коде. Лучше использовать условные операторы и выбрасывать явные исключения.

Как автоматизировать поиск синтаксических ошибок с помощью линтера?

Инструменты статического анализа, такие как pylint или flake8, проверяют код на соответствие стандартам PEP 8 и находят потенциальные проблемы до запуска. Например, отсутствие пробелов, неиспользуемые переменные, неопределённые имена.

# Файл buggy.py
def foo():
    x=1
    y=2
    print(x+y)
$ pylint buggy.py
************* Module buggy
buggy.py:1:0: C0114: Missing module docstring (missing-module-docstring)
buggy.py:1:0: C0116: Missing function or method docstring (missing-function-docstring)
buggy.py:2:4: C0326: Exactly one space required after comma (bad-whitespace)
buggy.py:3:4: C0326: Exactly one space required after comma (bad-whitespace)

Линтеры не только находят синтаксические ошибки, но и стилистические неточности. Для автоматического исправления некоторых проблем можно использовать autopep8 или black.

Линтеры часто выдают много предупреждений на ранних этапах. Настройка конфигурационного файла (например, .pylintrc) позволяет отключить несущественные проверки и сосредоточиться на ошибках.

Как найти ошибку, которая проявляется только в определённых условиях, с помощью модуля trace?

Модуль trace позволяет отследить выполнение каждой строки кода (трассировка строк). Это полезно для понимания, какие ветви кода выполняются, и поиска мест, где неожиданно пропускаются блоки.

# main.py
def process(data):
    if data > 0:
        print("positive")
    else:
        print("non-positive")

import sys
import trace

tracer = trace.Trace(count=False, trace=True)
tracer.run('process(-5)')
print("---")
tracer.run('process(10)')
 --- modulename: main, funcname: process
main.py(4): if data > 0:
main.py(7):         print("non-positive")
non-positive
 --- modulename: main, funcname: process
main.py(4): if data > 0:
main.py(5):         print("positive")
positive

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

При большом объёме кода трассировка создаёт огромный поток сообщений. Следует ограничивать её конкретной функцией или диапазоном строк с помощью параметров trace.

Расширенные примеры отладки Python

Как использовать pdb для анализа рекурсивной функции?

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

Пример
import pdb

def fib(n):
    pdb.set_trace()
    if n <= 1:
        return n
    return fib(n-1) + fib(n-2)

fib(3)

В консоли pdb можно использовать команды: n (next), s (step into), c (continue), p (print) для просмотра переменных. Например, в точке останова на входе в fib(3) можно напечатать p n, чтобы увидеть текущий аргумент.

(Pdb) p n
3
(Pdb) n
> /tmp/test.py(7)fib()
-> if n <= 1:
(Pdb) n
> /tmp/test.py(9)fib()
-> return fib(n-1) + fib(n-2)
(Pdb) s
--Call--
> /tmp/test.py(3)fib()
-> def fib(n):
(Pdb) p n
2
(Pdb) c
^C

Как отловить ошибку, возникающую внутри многопоточного кода?

Использование модуля threading затрудняет отслеживание ошибок, так как исключения в потоке не всплывают в основной поток. Решение - использовать concurrent.futures.ThreadPoolExecutor с методом result(), который пробрасывает исключение.

Пример
from concurrent.futures import ThreadPoolExecutor
import time

def worker(x):
    if x == 2:
        raise ValueError("Ошибка при x=2")
    return x * 2

with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [executor.submit(worker, i) for i in range(4)]
    for f in futures:
        try:
            print(f.result())
        except Exception as e:
            print(f"Поймано исключение: {e}")
0
2
Поймано исключение: Ошибка при x=2
6

Альтернатива - использовать queue.Queue для передачи исключений в основной поток.

Как использовать sys.settrace для тонкой настройки трассировки?

Функция sys.settrace позволяет установить собственный обработчик событий вызова функций, строк и исключений. Это мощный, но низкоуровневый инструмент, полезный для создания профилировщиков или мониторинга.

Пример
import sys

def trace_calls(frame, event, arg):
    if event == 'call':
        print(f"Вызов функции {frame.f_code.co_name} в строке {frame.f_lineno}")
    elif event == 'line':
        print(f"Строка {frame.f_lineno} в {frame.f_code.co_name}")
    return trace_calls

sys.settrace(trace_calls)

def add(a, b):
    return a + b

x = add(1, 2)
y = add(3, 4)
print(x, y)
sys.settrace(None)
Вызов функции add в строке 10
Строка 11 в add
Строка 12 в add
Вызов функции add в строке 10
Строка 11 в add
Строка 12 в add
3 7

Обратите внимание: при каждом событии возвращается та же функция, чтобы продолжить трассировку внутри вызванной функции. Для отключения трассировки в конкретной функции можно вернуть None.

Как выполнить отладку кода, который обрабатывает внешние данные (например, JSON из API)?

Часто ошибки связаны с некорректным форматом данных. Полезно использовать json.decoder.JSONDecodeError с трассировкой и выводить сырые данные для анализа.

Пример
import json

data = '{"name": "Alice", "age": 30'

try:
    obj = json.loads(data)
    print(obj)
except json.JSONDecodeError as e:
    print(f"Ошибка разбора JSON: {e}")
    print(f"Позиция ошибки: {e.pos}, строка: {e.lineno}, колонка: {e.colno}")
    # Визуализация ошибки
    lines = data.split('\n')
    for i, line in enumerate(lines):
        print(f"{i+1}: {line}")
        if i+1 == e.lineno:
            print(f"    {' ' * (e.colno - 1)}^--- здесь ошибка")
Ошибка разбора JSON: Expecting ',' delimiter: line 1 column 30 (char 29)
Позиция ошибки: 29, строка: 1, колонка: 30
1: {"name": "Alice", "age": 30
                              ^--- здесь ошибка

Данный приём помогает быстро определить, в каком месте структуры данных нарушен синтаксис.

Указание на ошибку в коде Python - comments

En
укажите на ошибку python (python)