17

A Python function has a code object __code__.

A sys.settrace trace frame has a f_code code object.

For those calls to the tracer that are functions, how can I get the function object (and its __annotation__ member)?

So far, by trial and error, I have:

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

This seems to work for functions, but not for class-member functions; worse, it confuses class-member functions with top-level functions of the same name.

(I'm on Python 3.2.3 (Xubuntu). I see that Python 3.3 inspect module has a signature function; will this return the annotation for a code object or does it too need a function object?)

2 Answers 2

9

Through the inspect.getframeinfo module. I mean -- there is no straightforward way of doing that in Python -- Most times you can get hold of the code object, without having the function already, it is through frame instrospection.

Inspect's getframeinfo function does return some information about the frame being run, then you can retrieve the function object by getting its name.

Tough this is implementation dependent and have some drawbacks:

>>> 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'

Another way, but still implementation dependent, is to use the gc module (garbage collector) to get referrers to said code object.

>>> 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>
>>> 

-- This is for Python 3 - for Python 2 one should replace __code__ by func_code

Sign up to request clarification or add additional context in comments.

4 Comments

the inspect.getframeinfo tuple's function field seems to be a string rather than a function object. And the gc approach risks multiple referrers?
I've ended up using gc.get_referrers(...)[0]
You can add a check to see if func_code/__code__ is the code object you want to the list comprehension.This still fails if you generate multiple functions with differing closures from the same code object, e.g. with a decorator. I suppose one could compare the f_locals against the cell_contents of the func_closure for each...
As for the multiple referrers: if there are multiple referrers that are function objects, then you in fact have multiple functions reusing the same code object, and have to deal with them anyway.
2

You can retrieve the function-object as an attribute of the module or the class:

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)

Output: call add {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

The solution is inspired by this question.

1 Comment

There's no guarantee that the self variable is named self, though, is there? It could contain something completely arbitrary.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.