Есть ли способ выполнить код при создании функции в CPython?

Есть ли способ подключить интерпретатор CPython, чтобы каждое создание функции (def, lambda) приводило к вызову процедуры, которую я определил? sys.settrace и sys.setprofile, к сожалению, не охватывают и def, и lambda.

Обновлять:

Кажется, в Python 3.7 есть f_trace_opcodes ... есть ли вариант для более ранних версий?

settrace не имеет конкретных событий для большинства типов операторов. Но в нем есть как события line, так и opcode. Итак, вы можете просто проверить, запускает ли строка def или код операции - MAKE_FUNCTION. (Для 2.7 или 3.5 вам нужно будет проверить, находится ли он в MAKE_FUNCTION или MAKE_CLOSURE, и, IIRC, даже более старые версии Python имели еще больше кодов операций для создания функций, но вы можете просто посмотреть документацию модуля dis вашей версии, чтобы увидеть. )
abarnert 08.09.2018 21:17

@abarnert: Ох, я смотрел только на Python 2, в котором нет событий кода операции. Интересно, посмотрю на Python 3. Спасибо!

user541686 08.09.2018 21:21

Во-первых, вы можете сделать это в терминах строк: вам просто нужно посмотреть на оператор, начинающийся с frame.f_lineno, и увидеть, является ли он def (хакерским путем, просто взглянув на что-то вроде inspect.getsource(frame).lstrip().startswith('def '), или, что более надежно, например, ast.parse с исходным кодом). чтобы узнать, является ли это узлом def. Или, даже если вас вызывают только один раз в строке, вы все равно можете посмотреть коды операций в этой строке, чтобы узнать, есть ли MAKE_FUNCTION.

abarnert 08.09.2018 21:24

@abarnert: Я думаю, что line рискованно по множеству причин, одна из которых заключается в том, что я не хочу пропускать лямбды (я не думал включать это в вопрос, но должен был сказать, что хочу подключить функцию творчество). opcode, похоже, то, что я хочу :)

user541686 08.09.2018 21:26

Хорошо, но обратите внимание на понимание (кроме listcomps в 2.x) - это определения функций.

abarnert 08.09.2018 21:27

@abarnert: Уф ... Я думаю, что для моих текущих вариантов использования я могу их игнорировать, но это действительно полезно знать, спасибо! Кроме того, я только что заметил, что f_trace_opcodes предназначен только для Python 3.7 ... Я должен упомянуть 3.6 и ранее в вопросе.

user541686 08.09.2018 21:28

Хорошо, если вы не используете 3.7+ и трассировки строк недостаточно, вы не можете сделать это с settrace. (Ваш вопрос в основном представляет собой запрос функции для новой функции, и тот факт, что они реализовали этот запрос функции за год до того, как вы попросили об этом, не помогает, если вы не используете последнюю версию ...) Вы, вероятно, все еще можете делать то, что вы хотите, написав сценарий отладчика или написав ловушку импорта, которая вставляет вызовы трассировки в узлы def / lambda в AST или в коды операций MAKE_FUNCTION в байт-коде, или что-то еще сложное, но ничего простого.

abarnert 08.09.2018 21:36

@Mehrdad, это звучит очень интересно: что будет делать ваш код при создании функции?

Ned Batchelder 08.09.2018 21:50
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
8
154
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В версиях до 3.7 нет эквивалента трассировке opcode. Если бы это было так, эта функция вообще не была бы добавлена ​​в 3.7.

Если вы можете перейти на 3.7, то все, что вам нужно, будет просто:

def tracefunc(frame, event, arg):
    if event == 'call':
        frame.f_trace_opcodes = True
    elif event == 'opcode':
        if frame.f_code.co_code[frame.f_lasti] == dis.opmap['MAKE_FUNCTION']:
            makefunctiontracefunc(frame)
    return tracefunc
sys.settrace(tracefunc)

Но если вы не можете… есть ряд более сложных вещей, которые вы делаете мог, в зависимости от того, по каким причинам вы этого хотите, но ни одна из них не является отдаленно простой:

  • Используйте трассировку line и проверяйте код до следующей строки. Это тривиально для def, но для lambda (и понимания 1) это будет большой проблемой, потому что lambda (или даже пять из них) могут появиться в середине утверждения. Вы можете ast.parse исходный код или исследовать байт-код, чтобы выяснить, есть ли внутри определенные функции, но все еще нет способа вызвать вашу ловушку прямо во время определения.
  • Вместо использования трассировки напишите ловушку импорта, которая изменяет код по мере его импорта. Самый простой способ сделать это, вероятно, находится на уровне AST: после синтаксического анализа источника используйте NodeTransformer для инъекции вызовов некоторой функции 2 до или после каждого узла def и lambda, а затем скомпилируйте преобразованное дерево. Но вы также можете сделать это на уровне байт-кода с помощью bytecode или byteplay, до или после каждого MAKE_FUNCTION.3.
  • Скрипт pdb вместо написания собственного отладчика. Я не уверен, что это вообще поможет, потому что pdb вообще не имеет возможности пройти через часть выражения.
  • Выполните отладку самого CPython и добавьте точку останова в обработчике MAKE_FUNCTION в цикле ceval, который вызывает ваш код. Конечно, ваш код находится в интерпретаторе отладчика, которым может быть Python для gdb и lldb, но это все еще не интерпретатор Python тем же, который вы отлаживаете. И хотя можно рекурсивно оценивать код в отлаженном интерпретаторе (или запускать его pdb), это непросто, и при его работе вы всегда сталкиваетесь с ошибками.

1. Comprehensions (except list comprehensions, in 2.x) are implemented by defining and then calling a function. So, any of the methods that rely on the MAKE_FUNCTION opcode or similar are going to also fire on comprehensions, while those that rely on source or AST parsing will not (unless you do so explicitly, of course).

2. Obviously you also need to inject an import at the top of every module to make that function available, or inject the function into the builtins module.

3. And MAKE_CLOSURE, for earlier versions of Python.

Другие вопросы по теме