TGViewer
Находки в опенсорсе Находки в опенсорсе @opensource_findings · 12.6K subscribers
Post #911 12K
unraisable exceptions в питоне

Мы все с вами привыкли, что в питоне можно "зарайзить" исключение в любой момент: raise Exception
Но, что если в какой-то момент времени мы не можем вызывать исключение?

Простейший пример: что произойдет при запуске такого скрипта?


# ex.py
class BrokenDel:
def __del__(self):
raise ValueError('del is broken')

obj = BrokenDel()
del obj

print('done!') # будет ли выведено?


Тут может быть два варианта:
1. Или del вызовет ValueError и программа завершится
2. Или случится какая-то магия, ошибка будет вызвана, напечатается, но программа продолжится

Ну и так как мы с вами на том канале, где мы с вами, то конечно же будет второй вариант.


» ./python.exe ex.py
Exception ignored while calling deallocator <function BrokenDel.__del__ at 0x10303c1d0>:
Traceback (most recent call last):
File "/Users/sobolev/Desktop/cpython/ex.py", line 3, in __del__
raise ValueError('del is broken')
ValueError: del is broken
done!


Знакомьтесь – unraisable exceptions 🤝

Как оно работает?

В некоторых местах C кода у нас есть необходимость вызывать исключения, но нет технической возможности. Пример, как выглядит упрощенный dealloc для list?


static void
list_dealloc(PyListObject *op)
{
Py_ssize_t i;
PyObject_GC_UnTrack(op); // убираем объект из отслеживания gc
if (op->ob_item != NULL) {
i = Py_SIZE(op);
while (--i >= 0) {
Py_XDECREF(op->ob_item[i]); // уменьшаем счетчик ссылок каждого объекта в списке
}
op->ob_item = NULL;
}
PyObject_GC_Del(op);
}


А, как вы можете знать, чтобы в C коде вызвать ошибку, нужно сделать две вещи:
- Взывать специальное АПИ вроде PyErr_SetString(PyExc_ValueError, "some text")
- И вернуть NULL как PyObject * из соответствующих АПИ, показывая, что у нас ошибка. Если вернуть NULL нельзя, то мы не можем поставить ошибку. А тут у нас void и вернуть вообще ничего нельзя. Потому приходится использовать вот такой подход с unraisable exception

Ошибку мы "вызываем" через специальные АПИ:
- PyErr_WriteUnraisable
- PyErr_FormatUnraisable

Они создают ошибку, но не выкидывают её обычным способом, а сразу отправляют в специальный хук-обработчик.
В питоне оно используется где-то 150 раз. То есть – прям часто. Примеры:

- Ошибки при завершении интерпретатора, попробуйте сами:


import atexit
def foo():
raise Exception('foo')
atexit.register(foo)


- Ошибки внутри sys.excepthook
- Ошибки внутри gc
- Ошибки внутри логики установки ошибок (вдруг память кончилась, например) 🌚️️️️
- И многое другое

Пользовательское АПИ

Ну и конечно же, есть специальный хук для обработки таких ошибок: sys.unraisablehook

Например, pytest использует кастомный хук, чтобы валить тесты при возникновении такой ситуации. Что логично.

Обсуждение: знали ли вы про такую особенность? Приходилось ли где-то в мониторинге особо настраивать?

| Поддержать | YouTube | GitHub | Чат |
Python documentation Exception Handling The functions described in this chapter will let you handle and raise Python exceptions. It is important to understand some of the basics of Python exception handling. It works somewhat like the PO...
  • 🤯 112
  • 🔥 36
  • 👍 28
  • ❤ 4
  • 👎 1
  • 🤡 1
More from @opensource_findings
  1. Sep 25, 2026Монадические выражения в Python. Наконец-то! Продолжаем разговор про интересные PEPы. Пого…
  2. Sep 15, 2026PEPы в Python окончательно вышли из-под контроля Давайте посмотрим, что происходит с ПЕПам…
  3. Sep 10, 2026Большая бесплатная конференция в Нижнем Новгороде 17 октября Регистрация: https://itgorky.…
  4. Sep 1, 2026Post #985
  5. Aug 24, 2026JIT могут удалить из CPython! PEP-836: https://peps.python.org/pep-0836 JIT в CPython имее…
  6. Aug 18, 2026Как AWS у опенсорсера пакеты отжимал Нерегулярная рубрика "посмотрите, что творится!". Дан…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →