Получение функции Python для объекта кода

Функция Python имеет объект кода __code__.

Трассировка sys.settrace frame имеет объект кода f_code.

Для тех вызовов трассировщика, которые являются функциями, как я могу получить объект функции (и его член __annotation__)?

На данный момент, методом проб и ошибок, у меня получилось:

if hasattr(frame.f_globals.get(frame.f_code.co_name),"__annotations__"):

Кажется, это работает для функций, но не для функций-членов класса; хуже того, он путает функции-члены класса с одноименными функциями верхнего уровня.

(Я использую Python 3.2.3 (Xubuntu). Я вижу, что модуль Python 3.3 inspect имеет signature; вернет ли она аннотацию для объекта кода или ей тоже нужен объект-функция?)


person Will    schedule 08.10.2012    source источник


Ответы (2)


Через модуль inspect.getframeinfo. Я имею в виду - в Python нет простого способа сделать это - в большинстве случаев вы можете получить объект кода, не имея функции, это происходит через интроспекцию фрейма.

Функция Inspect getframeinfo возвращает некоторую информацию о запущенном фрейме, после чего вы можете получить объект функции, получив его имя.

Это зависит от реализации и имеет некоторые недостатки:

>>> import inspect
>>> def a():
...   return inspect.currentframe()
... 

>>> inspect.getframeinfo(a())
Traceback(filename='<stdin>', lineno=2, function='a', code_context=None, index=None)
>>> b = inspect.getframeinfo(a())
>>> b.function
'a'

Другой способ, но все еще зависящий от реализации, заключается в использовании модуля gc (сборщика мусора) для получения ссылок на указанный объект кода.

>>> import gc
>>> from types import FunctionType
>>> def a(): pass
... 
>>> code = a.__code__

>>> [obj for  obj in  gc.get_referrers(code) if isinstance(obj, FunctionType)  ][0]
<function a at 0x7f1ef4484500>
>>> 

-- Это для Python 3 - для Python 2 следует заменить __code__ на func_code

person jsbueno    schedule 08.10.2012
comment
поле function кортежа inspect.getframeinfo кажется строкой, а не функциональным объектом. И подход gc рискует несколькими реферерами? - person Will; 08.10.2012
comment
В итоге я использовал gc.get_referrers(...)[0] - person Will; 09.10.2012
comment
Вы можете добавить проверку, чтобы увидеть, является ли func_code/__code__ объектом кода, который вы хотите использовать для понимания списка. Это все равно не удается, если вы создаете несколько функций с разными закрытиями из одного и того же объекта кода, например. с декоратором. Я полагаю, можно было бы сравнить f_locals с содержимым ячейки func_closure для каждого... - person Walter Mundt; 26.11.2013
comment
Что касается нескольких рефереров: если есть несколько рефереров, которые являются функциональными объектами, то у вас фактически есть несколько функций, повторно использующих один и тот же объект кода, и вам все равно придется с ними иметь дело. - person jsbueno; 11.05.2015

Вы можете получить объект-функцию как атрибут модуля или класса:

import inspect
import sys


def frame_to_func(frame):
    func_name = frame.f_code.co_name
    if "self" in frame.f_locals:
        return getattr(frame.f_locals["self"].__class__, func_name)
    else:
        return getattr(inspect.getmodule(frame), func_name)


def tracefunc(frame, event, arg):
    if event in ['call', 'return']:
        func_obj = frame_to_func(frame)
        print(f"{event} {frame.f_code.co_name} {func_obj.__annotations__}")


def add(x: int, y: int) -> int:
    return x+y


if __name__ == '__main__':
    sys.settrace(tracefunc)
    add(1, 2)
    sys.settrace(None)

Выход: call add {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

Решение основано на этом вопросе.

person M. Gruber    schedule 24.04.2020