Как бороться с ограничениями «inspect.getsource» - или как получить ТОЛЬКО источник функции? - PullRequest
2 голосов
/ 11 марта 2019

Я играл с модулем inspect из стандартной библиотеки Python.

Следующие примеры работают нормально (при условии, что inspect было импортировано):

def foo(x, y):
    return x - y
print(inspect.getsource(foo))

... выведет def foo(x, y):\n return x - y\n и ...

bar = lambda x, y: x / y
print(inspect.getsource(bar))

... напечатает bar = lambda x, y: x / y\n.Все идет нормально.Однако в следующих примерах все становится немного странно:

print(inspect.getsource(lambda x, y: x / y))

... будет печатать print(inspect.getsource(lambda x, y: x / y)) и ...

baz = [2, 3, lambda x, y: x / y, 5]
print(inspect.getsource(baz[2]))

... будет печатать baz = [2, 3, lambda x, y: x / y, 5].

Шаблон выглядит так, что весь соответствующий исходный код строки независимо от контекста возвращается getsource.Все остальное в этих строках, в моем случае, кроме материала / определения желаемой функции, также включено.Есть ли другой, «альтернативный» подход, который позволил бы извлечь что-то, представляющее исходный код функции - и только ее исходный код - предпочтительно каким-то анонимным способом?


EDIT (1)

def foo(x, y):
    return x - y
bar = [1, 2, foo, 4]
print(inspect.getsource(bar[2]))

... напечатает def foo(x, y):\n return x - y\n.

1 Ответ

3 голосов
/ 27 марта 2019

К сожалению, это невозможно с inspect, и вряд ли будет работать без повторного анализа (и компиляции) исходного кода.inspect s getsource метод довольно ограничен: он использует getsourcelines для вызова затем findsource, что по существу разворачивает ваш объект до тех пор, пока мыв итоге получим PyCodeObject.

На этом этапе мы имеем дело с скомпилированным байт-кодом.Все, что осталось от исходного источника , это фрагменты и подсказки, такие как , например co_firstlineno:

/* Bytecode object */
typedef struct {
    /* ... other fields omitted ... */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    /* ... other fields omitted ... */
} PyCodeObject;

Кстати, аналогично PyCodeObject,PyFrameObject также содержит только столбец f_lineno, но no , который объясняет, почему в трассировках указываются только имя файла и строка: столбец не компилируется вбайт-код.

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

inspect использует общедоступную информацию (co_firstlineno) и , а затем просто ищет подходящее началофункция и конец окружающего блока .Тем не менее, inspect это почти там, но он находит только любой блок, а не правильный , и он не может найтиправильный на данный момент.inspect токенизирует полную строку и не начинается с правильного варианта, он также не будет знать правильную соответствующую область исходного кода.

Скажем, у нас есть

plus, minus, mult = lambda x: x + 1, lambda y: y - 1, lambda z: z * 5

и мыхочу просто minus.Поскольку байт-код не содержит co_firstcolumn, у нас есть только полная доступная строка.Мы могли бы разобрать все лямбды, но мы до сих пор не знаем, какая лямбда подходит нашему co_code.Нам нужно было бы скомпилировать их снова и проверить, соответствует ли их байт-код оригинальному.

В конце мы должны сделать именно это: снова проанализировать источник и найти правильный PyCodeObject.Было бы намного проще, если бы у нас был хотя бы начальный номер столбца, поскольку мы могли бы просто использовать синтаксический анализ, но AST сохраняет только номеров строк на данный момент .Так что либо inspect нужен большой патч, либо байт-код должен включать начальный столбец скомпилированного объекта.

...