You are always looking at a function context; the method context is already gone by the time the function executes. Technically speaking, functions act as descriptors when accessed as attributes on an instance (instance.method_name
), which return a method object. The method object then, when called, in turn calls the original function with the instance as the first argument. Because this all happens in C code, no trace of the original method object remains on the Python stack.
The stack only needs to keep track of namespaces and the code to be executed for the local namespace. The original function object is no longer needed for these functions, only the attached code object still retains the name of the original function definition for traceback purposes.
If you know the function to be a method, you could search for a self
local name. If present, type(self)
is the class of the instance, which is not necessarily the class the method was defined on.
You would then have to search the class and it's bases (looping over the .__mro__
attribute) to try and locate what class defined that method.
There are a few more snags you need to take into account:
Naming the first argument of a method self
is only a convention. If someone picked a different name, you won't be able to figure that out without parsing the Python source yourself, or by going up another step in the stack and trying to deduce from that line how the function was called, then retrieve the method and it's .im_self
attribute.
You only are given the original name of the function, under which it was defined. That is not necessarily the name under which it was invoked. Functions are first-class objects and can be added to classes under different names.
Although a function was passed in self
and was defined as part of a class definition, it could have been invoked by manually passing in a value for self
instead of the usual route of the method (being the result of the function acting as a descriptor) passing in the self
reference for the caller.
In Python 3.3 and up, the situation is marginally better; function objects have a new __qualname__
attribute, which would include the class name. The problem is then that you still need to locate the function object from the parent stack frame.