Покращена діагностика винятків у Python 3.11
У великих програмах складно зрозуміти по роздруківці стека викликів, де саме відбулася помилка в коді. Тим більше, якщо використовуються складні вирази. У Python 3.11 ввели докладну діагностичну інформацію у роздруківку стека викликів. Розглянемо кілька прикладів.
На момент написання статті Python 3.11 ще офіційно не вийшов. Можна встановити версію 3.11 окремо зі сторінки пререлізів.
Перевірити версію Python можна опцією -version
в консолі:
% python --version
Python 3.11.0b3
Розглянемо невеликий приклад (файл main.py
) у якому потенційно може виникнути виняток ZeroDivisionError
у функції f1
:
# main.py
import sys
def f1(a: float, b: float, c: float) -> float:
return a / b / c
a1 = float(sys.argv[1])
b1 = float(sys.argv[2])
c1 = float(sys.argv[3])
print(f"Result: {f1(a1, b1, c1) * sum([a1, b1, c1])}")
Запустимо цей приклад із командного рядка. Тут ми навмисне передаємо нуль як один з аргументів, щоб отримати виняток ZeroDivisionError
:
python main.py 1 0 2
Як виглядає роздруківка стека викликів у Python 3.10:
Traceback (most recent call last):
File "main.py", line 12, in <module>
print(f"Result: {f1(a1, b1, c1) * sum([a1, b1, c1])}")
File "main.py", line 6, in f1
return a / b / c
ZeroDivisionError: float division by zero
Зверніть увагу: інтерпретатор повідомляє про виключення ZeroDivisionError
у 6 рядку, але не вказує в якому саме операторі поділу: першому чи другому?
Запустимо той самий код з тими самими аргументами в інтерпретаторі Python 3.11. Як виглядає роздруківка стека викликів у Python 3.11:
Traceback (most recent call last):
File "main.py", line 12, in <module>
print(f"Result: {f1(a1, b1, c1) * sum([a1, b1, c1])}")
^^^^^^^^^^^^^^
File "main.py", line 6, in f1
return a / b / c
~~^~~
ZeroDivisionError: float division by zero
На що в цьому друку слід звернути увагу:
- символом
~~^~~
підкреслено, що у першому операторі відбувається розподіл на нуль:a / b
; - Символом
^^^^^^^^^^^^^^
підкреслена та частина складного виразу, при обчисленні якої стався виняток:f1(a1, b1, c1)
.
Спробуємо запустити приклад в інтерпретаторі Python 3.11 з іншими аргументами:
python main.py 1 2 0
Тепер інтерпретатор вкаже на другий оператор поділу:
File "main.py", line 6, in f1
return a / b / c
~~~~~~^~~
ZeroDivisionError: float division by zero
Робота зі складними словниками – це ще один приклад, де покращена діагностика допоможе швидше знайти помилку. Розглянемо JSON-документ data.json
та код файлу account.py
, який підсумовує баланси рахунків account1
та account2
. Зверніть увагу, що в цьому прикладі навмисне немає транзакцій в account2.
{
"account1": {
"transactions": [
{"id": "1", "title": "Покупка", "sum": 123, "balance": 1000},
{"id": "2", "title": "Покупка", "sum": 800, "balance": 200},
{"id": "3", "title": "Покупка", "sum": 15, "balance": null}
]
},
"account2": {
"transactions": null
}
}
# account.py
import json
with open('data.json') as f:
data = json.load(f)
summa = data['account1']['transactions'][0]['balance'] + data['account2']['transactions'][0]['balance']
print(f"Сумма счетов: {summa}")
Якщо ми запустимо цей скрипт інтерпретатором Python 3.10, то отримаємо таку роздруківку стека викликів:
Traceback (most recent call last):
File "account.py", line 7, in <module>
summa = data['account1']['transactions'][0]['balance'] + data['account2']['transactions'][0]['balance']
TypeError: 'NoneType' object is not subscriptable
Тут зрозуміло, що виняток відбувається у 7 рядку, але незрозуміло, про який саме об'єкт йдеться? Адже вираз складний. Потрібно перезапускати програму під налагоджувачем, ставити точку зупинки та дивитися стан внутрішніх змінних.
Якщо ми запустимо цей скрипт інтерпретатором Python 3.11, то отримаємо таку роздруківку стека викликів:
File "account.py", line 7, in <module>
summa = data['account1']['transactions'][0]['balance'] + data['account2']['transactions'][0]['balance']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
TypeError: 'NoneType' object is not subscriptable
Тут за допомогою символу ^^^
показано який саме оператор викликав виняток: результат виразу data['account2']['transactions']
дорівнює None
і до нього не можна застосувати [0]
.
Повну інформацію про можливості детальної діагностики у винятках можна прочитати в PEP 657 – Include Fine Grained Error Locations in Tracebacks.