Получение номера строки оператора возврата - PullRequest
0 голосов
/ 05 февраля 2019

Есть ли способ получить номер строки оператора, возвращаемого функцией Python программно?Давайте рассмотрим следующий пример:

def foo(i: int) -> str:
    if i == 1:
        return 'he'
    elif i == 2:
        return 'ha'
    return 'he'

Если ввести 1 или 3, функция вернет 'he'.Однако я не буду знать, какой return 'he' на самом деле выполняется, если не смогу пройтись по коду.

Мое приложение позволяет пользователям вводить бизнес-правила в Python из графического интерфейса и позволять им самим проверять правила.Поскольку мое приложение не может пошагово выполнять код (оно делегирует задачу выполнения автономному движку Python и ожидает строку или исключение.) Я хочу найти способ получить номер возвращаемой строки, чтобы выделитьсоответствующая строка в редакторе графического интерфейса.

Грязным способом было бы иметь функцию, возвращающую кортеж из номера строки и строки, но в идеале я хотел бы избавить пользователей от необходимости делать это.

Ответы [ 2 ]

0 голосов
/ 05 февраля 2019

Вы можете попросить Python сообщить вам о любых возвратах, используя sys.settrace();это подключаемая функция, которую Python будет вызывать при возникновении определенных событий, и как обычные отладчики и профилировщики подключаются к Python.

Функция, которую вы регистрируете с помощью sys.settrace(), будет вызываться для call только события, когда Python входит в новую локальную область (для вызовов функций, тел классов, а также для выражений и выражений генератора).Затем вы можете вернуть None (не отслеживать эту локальную область) или функцию трассировки, которая будет использоваться для line , исключение или return события в этой области.В Python 3.7 вы можете установить параметры объекта frame для дальнейшего контроля того, на каком уровне детализации вызывается ваша функция трассировки для каждой области;Вы можете отключить события для каждой строки или даже включить события для каждого кода операции.

Вы можете использовать его для записи возврата таких событий;Я использую один метод трассировки для call и return events:

import inspect
import sys

class ReturnLines:
    def __init__(self):
        self.returns = []
        self._old_trace = None

    def start(self):
        self._old_trace = sys.gettrace()
        sys.settrace(self.trace)

    def stop(self):
        sys.settrace(self._old_trace)

    def __enter__(self):
        self.start()
        return self.returns

    def __exit__(self, *exc):
        self.stop()

    def trace(self, frame, event, arg):
        filename = None
        if frame is not None:
            filename = inspect.getsourcefile(frame)
        if event == 'call':
            if filename == __file__:
                # skip ourselves
                return
            try:
                # Python 3.7+: only trace exceptions and returns for this call
                frame.f_trace_lines = False
            except AttributeError:
                pass
            return self.trace
        elif event == 'return':
            self.returns.append((filename, frame.f_lineno, arg))

Поместите это в отдельный модуль и используйте объект как менеджер контекста:

from return_recorder import ReturnLines

with ReturnLines() as return_lines:
    # run the code you want to trace
    # ...

Диспетчер контекста предоставляет вам доступ к объекту списка, к которому он добавляет возвраты (как (filename, linenumber, returned_object) кортежи), так что вы можете получить доступ к информации возврата при выполнении кода внутри диспетчера контекста:

>>> from return_recorder import ReturnLines
>>> def foo(i: int) -> str:
...     if i == 1:
...         return 'he'
...     elif i == 2:
...         return 'ha'
...     return 'he'
...
>>> with ReturnLines() as return_lines:
...     for i in range(3):
...         foo(i)
...         print(f'<-- i={i}, returned at line {return_lines[-1][1]}')
...
'he'
<-- i=0, returned at line 6
'he'
<-- i=1, returned at line 3
'ha'
<-- i=2, returned at line 5
>>> for filename, lineno, returned in return_lines:
...     print(f'{filename}:{lineno}:{returned!r}')
...
None:6:'he'
None:3:'he'
None:5:'ha'

Для интерактивного переводчика имя файла: None.

Модуль bdb, лежащий в основе ответа Кевина, основан на sys.set_trace(), но не отключает трассировку строк в Python 3.7+.,И как универсальная инфраструктура отладчика, она добавляет более высокие издержки на событие трассировки.Это приводит к более медленному выполнению кода, который вы используете.

0 голосов
/ 05 февраля 2019

Модуль bdb позволяет вам проверять каждый кадр непосредственно перед его возвратом, поэтому вы сможете получить f_lineno окончательного возврата foo в этой точке.Пример:

from bdb import Bdb
class ReturnWatcher(Bdb):
    def __init__(self):
        self.last_encountered_return_line = None
        super().__init__()
    def user_return(self, frame, return_value):
        self.last_encountered_return_line = frame.f_lineno

def foo(i):
    if(i==1):
        return 'he'
    elif(i==2):
        return 'ha'
    return 'he'

x = ReturnWatcher()
x.runcall(foo, 1)
print("Last return statement executed on line", x.last_encountered_return_line)
x.runcall(foo, 2)
print("Last return statement executed on line", x.last_encountered_return_line)
x.runcall(foo, 3)
print("Last return statement executed on line", x.last_encountered_return_line)

Результат:

Last return statement executed on line 11
Last return statement executed on line 13
Last return statement executed on line 14
...