Согласно официальным документам для sys.exc_info
, для получения кортежа (type, value, traceback)
вам нужно исключение в любом фрейме стека. Если исключение не обрабатывается, вы получаете кортеж со значениями None
. Кадр стека может быть: текущим стеком или стеком вызова для функции или самой вызывающей функции (функции). При ведении журнала мы имеем дело только с traceback
для текущего стека (уведомление sys.exc_info()[2]
) и поэтому должны вызвать исключение для доступа к значениям кортежа. Вот выдержка из документов:
Эта функция возвращает кортеж из трех значений, которые дают информацию об исключении, которое в настоящее время обрабатывается. Возвращаемая информация относится как к текущему потоку, так и к текущему кадру стека. Если текущий кадр стека не обрабатывает исключение, информация берется из вызывающего кадра стека или его вызывающего, и так далее, пока не будет найден кадр стека, который обрабатывает исключение. Здесь «обработка исключения» определяется как «выполнение исключения». Для любого фрейма стека доступна только информация об обрабатываемом в данный момент исключении.
Если никакое исключение не обрабатывается где-либо в стеке, кортеж
содержит три значения None. В противном случае значения
возвращаются (тип, значение, трассировка). Их значение таково: тип получает
тип обрабатываемого исключения (подкласс BaseException);
значение получает экземпляр исключения (экземпляр типа исключения);
traceback получает объект traceback (см. Справочное руководство), который
инкапсулирует стек вызовов в точке, где исключение
первоначально произошло.
sys._getframe ([глубина]) возвращает объект фрейма из стека вызовов. Если задано необязательное целое число глубина , вернуть объект фрейма, который много раз вызывается ниже вершины стека. Значение по умолчанию для глубины равно нулю, возвращая кадр в верхней части стека вызовов.
Другим важным моментом, который следует учитывать, является то, что эта функция не гарантированно существует во всех реализациях Python . Мы знаем, что у CPython есть это. Следующий фрагмент кода из logging/__init__.py
выполняет эту проверку. Обратите внимание, что currentframe()
является лямбда-функцией .:
if hasattr(sys, '_getframe'):
currentframe = lambda: sys._getframe(3)
Это означает: , если sys._getframe () существует в реализации Python, вернуть объект 3-го кадра из верхней части стека вызовов . Если sys
не имеет этой функции в качестве атрибута, приведенный ниже оператор else
вызывает исключение для захвата объекта фрейма из Traceback
.
.
else: #pragma: no cover
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except Exception:
return sys.exc_info()[2].tb_frame.f_back
Чтобы лучше понять эту концепцию, я использовал приведенный выше код if-else
, чтобы создать пример (без каламбура). Это вдохновлено превосходным объяснением здесь . Следующий пример содержит 3 функции, которые были сохранены в файле с именем main.py
.
#main.py
def get_current_frame(x):
print("Reached get_current_frame")
if hasattr(sys, '_getframe'):
currentframe = lambda x: sys._getframe(x)
else: #pragma: no cover
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
raise Exception
except Exception:
return sys.exc_info()[2].tb_frame.f_back
return currentframe
def show_frame(num, frame):
print("Reached show_frame")
print(frame)
print(" frame = sys._getframe(%s)" % num)
print(" function = %s()" % frame(num).f_code.co_name)
print(" file/line = %s:%s" % (frame(num).f_code.co_filename, frame(num).f_lineno))
def test():
print("Reached test")
for num in range(4):
frame = get_current_frame(num)
show_frame(num, frame)
#function call
test()
Запустив этот код с python main.py
, мы получим следующий вывод:
Reached test
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0AE8>
frame = sys._getframe(0)
function = <lambda>()
file/line = main.py:74
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0B70>
frame = sys._getframe(1)
function = show_frame()
file/line = main.py:96
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0AE8>
frame = sys._getframe(2)
function = test()
file/line = main.py:89
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0B70>
frame = sys._getframe(3)
function = <module>()
file/line = main.py:115
Объяснение
Функция get_current_frame (x) : эта функция содержит тот же код из оператора if-else
из logging/__init__.py
. Единственное отличие состоит в том, что мы передаем аргумент глубина x
в функцию, которая используется функцией lambda
для захвата объекта кадра на этой глубине : currentframe = lambda: sys._getframe(x)
.
Функция show_frame (число, кадр) : эта функция print
s объект кадра , вызов функции кадра с ее глубиной , sys._getframe(num)
,имя функции вызывающей стороны, например.show_frame()
.. и т.д., имя файла, который выполняет код вызывающей функции вместе с текущей строкой №.в коде вызывающей функции.f_code
является атрибутом объекта фрейма, возвращаемого sys._getframe()
, и является объектом кода.co_name
является атрибутом этого объекта кода и возвращает имя, с которым был определен объект кода (вы можете напечатать f_code
, чтобы проверить это).Точно так же co_filename
извлекает имя файла и f_lineno
извлекает номер текущей строки.Вы можете найти объяснение этих атрибутов в inspect документах, которые также используются для интересного получения объектов кадра.Вы также можете написать некоторый изолированный код, чтобы понять, как работают эти атрибуты.Например,код ниже получает текущий кадр frameobj
(то есть: объект кадра в верхней части стека, глубина 0 (по умолчанию)) и печатает имя файла объекта кода для этого кадра (я запускаю этот код в main_module.py
).
import sys
frameobj = sys._getframe()
print(frameobj.f_code.co_filename)
#output:
main_module.py
Стек вызовов не слишком глубок, потому что есть только один вызов функции для _getframe()
.Если мы изменим код для получения кадра на глубине 1, мы получим ошибку:
Traceback (most recent call last):
File "main_module.py", line 3, in <module>
frameobj = sys._getframe(1)
ValueError: call stack is not deep enough
Function test () : эта функция получает текущий кадробъект на глубину num
в некотором диапазоне, а затем вызывает show_frame()
для этого num
и фреймовый объект.
Когда вызывается test()
, стек вызовов: test -> get_current_frame -> show_frame .В последующих вызовах стек равен get_current_frame ---> show_frame до тех пор, пока цикл for
не завершится для диапазона (4) в test()
.Если мы рассмотрим вывод сверху, кадр в верхней части стека имеет глубину 0: frame = sys._getframe(0)
, а вызывающая функция - это сама лямбда-функция.Линия №74 в file/line = main.py:74
- текущая строка №.когда эта функция была вызвана (представьте, что это последняя позиция курсора для этого кадра).Наконец, мы смотрим на кадр в нижней части стека.Это также объект фрейма (с глубиной 3), который используется при ведении журнала:
Reached get_current_frame
Reached show_frame
<function get_current_frame.<locals>.<lambda> at 0x0000000002EB0B70>
frame = sys._getframe(3)
function = <module>()
file/line = main.py:115
При ведении журнала нам нужна глубина 3, чтобы достичь стекового фрейма функции вызывающей стороны.
Мы также можем использовать наш предыдущий пример игрушки, чтобы понять эту концепцию.Поскольку стек не слишком глубокий, мы получаем текущий кадр на глубина 0.
import sys
frameobj = sys._getframe()
print(frameobj.f_code.co_name)
#Output:
<module>
Теперь, что если моя реализация Python не имеет атрибута _getframe()
для sys
?В этом случае код в else
будет выполнен и вызовет исключение, чтобы получить текущий кадр из traceback
.Следующая функция делает это, и вызывающая функция здесь снова равна <module>
(обратите внимание на вывод):
def currentframe():
"""Return the frame object for the caller's stack frame."""
try:
# test = 'x' + 1
raise Exception
except Exception:
_type, _value, _traceback = sys.exc_info()
print("Type: {}, Value:{}, Traceback:{}".format(_type, _value, _traceback))
print("Calling function:{}, Calling file: {}".format(sys.exc_info()[2].tb_frame.f_back.f_code.co_name, sys.exc_info()[2].tb_frame.f_back.f_code.co_filename))
return sys.exc_info()[2].tb_frame.f_back
currentframe()
#Output:
Type: <class 'Exception'>, Value:, Traceback:<traceback object at 0x0000000002EFEB48>
Calling function:<module>, Calling file: main.py
f_back
возвращает объект фрейма для фрейма трассировки tb_frame
, возвращенного текущим исключением.Мы можем проверить это, напечатав инструкцию return: print(sys.exc_info()[2].tb_frame.f_back)
и получим что-то вроде: <frame object at 0x000000000049B2C8>
Это объясняет, как модуль протоколирования захватывает текущий кадр.
Итак, где находится currentframe()
позже используется в журнале исходного кода?Вы найдете его здесь:
def findCaller(self, stack_info=False):
"""
Find the stack frame of the caller so that we can note the source
file name, line number and function name.
"""
f = currentframe()
#<----code---->
Приведенная выше функция получает текущий кадр функции вызывающей стороны и позже использует эту информацию, чтобы получить те же атрибуты (имя файла и т. Д.), К которым мы обращались ранее.