Почему исходный код модуля журналирования Python вызывает исключение в try? - PullRequest
0 голосов
/ 23 января 2019

При чтении исходного кода модуля журналирования Python я нашел код, подобный следующему:

# next bit filched from 1.5.2's inspect.py
def currentframe():
    """Return the frame object for the caller's stack frame."""
    try:
        raise Exception
    except:
        return sys.exc_info()[2].tb_frame.f_back

почему?Это равно этому?

def currentframe():
    return sys.exc_info()[2].tb_frame.f_back

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Согласно официальным документам для 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---->

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

0 голосов
/ 23 января 2019

Очевидно - exc_info (информация об исключении) отсутствует, пока не возникнет какое-либо исключение.Поэтому нам нужно вызвать исключение, чтобы получить доступ к его информации и получить от него стек вызовов.

Кажется, это самый простой способ получить доступ к текущему стеку вызовов.

...