Ответ 1
Сообщение об исключении действительно предлагает вам подсказку. Сравните вариант без распаковки:
>>> import sys
>>> sys.setrecursionlimit(4) # to get there faster
>>> def f(): f()
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in f
File "<stdin>", line 1, in f
File "<stdin>", line 1, in f
RuntimeError: maximum recursion depth exceeded
с:
>>> def f(): f(*())
...
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in f
File "<stdin>", line 1, in f
RuntimeError: maximum recursion depth exceeded while calling a Python object
Обратите внимание на добавление while calling a Python object
. Это исключение относится к функции PyObject_CallObject()
. Вы не увидите это исключение, если вы установите ограничение нечетной рекурсии:
>>> sys.setrecursionlimit(5)
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in f
File "<stdin>", line 1, in f
RuntimeError: maximum recursion depth exceeded
потому что это особое исключение, выраженное в ceval.c
код оценки фрейма внутри PyEval_EvalFrameEx()
:
/* push frame */
if (Py_EnterRecursiveCall(""))
return NULL;
Обратите внимание на пустое сообщение. Это ключевое различие.
Для вашей "регулярной" функции (без переменных аргументов) происходит то, что выбран оптимальный путь; функция Python, которая не нуждается в поддержке распаковки аргументов кортежа или ключевого слова, обрабатывается непосредственно в fast_function()
function цикла оценки. Создается новый программный объект с байт-кодом Python для этой функции и запускается. Это одна проверка рекурсии.
Но для вызова функции с переменными аргументами (кортежем или словарем или обоими) вызов fast_function()
не может быть использован. Вместо этого используется ext_do_call()
(расширенный вызов), который обрабатывает распаковку аргументов, а затем использует PyObject_Call()
, чтобы вызвать функцию. PyObject_Call()
выполняет проверку предела рекурсии и "вызывает" объект функции. Объект функции вызывается с помощью функции function_call()
, которая вызывает PyEval_EvalCodeEx()
, который вызывает PyEval_EvalFrameEx()
, что делает вторую проверку предела рекурсии.
TL; версия DR
Функции Python, вызывающие функции Python, оптимизируются и обходят функцию PyObject_Call()
C-API, если не происходит распаковка аргументов. Выполнение календаря Python и PyObject_Call()
делают реверсионные предельные тесты, поэтому в обход PyObject_Call()
избегается приращение проверки ограничения рекурсии на вызов.
Дополнительные места с проверкой глубины рекурсии
Вы можете grep исходный код Python для Py_EnterRecursiveCall
для других мест, где выполняются проверки глубины рекурсии; различные библиотеки, такие как json
и pickle
, используют его, чтобы избежать синтаксического анализа структур, которые являются слишком глубоко вложенными или рекурсивными, например. Другие проверки размещаются в реализациях list
и tuple
__repr__
, богатых сопоставлениях (__gt__
, __lt__
, __eq__
и т.д.), Обрабатывая __call__
вызываемый объектный крючок и обработку __str__
вызовы.
Таким образом, вы можете достигнуть предела рекурсии гораздо быстрее:
>>> class C:
... def __str__(self):
... global depth
... depth += 1
... return self()
... def __call__(self):
... global depth
... depth += 1
... return str(self)
...
>>> depth = 0
>>> sys.setrecursionlimit(10)
>>> C()()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in __call__
File "<stdin>", line 5, in __str__
RuntimeError: maximum recursion depth exceeded while calling a Python object
>>> depth
2