Python – Getting Class Name of Calling Method

pythonpython-3.x

I know how to get a caller method name (from here: How to get the caller's method name in the called method?)

import sys
print sys._getframe().f_back.f_code.co_name

What I'd like to get is the class name this method belongs to (assuming that it is in the class).
So if:

def get_some_info():
    print('Class name of caller:', XXX)

class Base:
     def my_method(self):
         get_some_info()

class A(Base):
     pass

class B(Base):
     pass

a = A()
b = B()
a.my_method()
b.my_method()

should return:

 ... A
 ... B

What should I do in xxx?

I tried (using info on _getframe) do something like:

 sys._getframe().f_back.f_code.__self__

but it doesn't work

UPDATE:

I cannot pass class name to a called function (otherwise that would be easy, but thanks to all who suggested this solution!)

Best Answer

You can get the calling frame object with inspect.currentframe() and get the object that self is bound to through its f_locals attribute:

import inspect

def get_some_info():
    # get the call frame of the calling method
    frame = inspect.currentframe().f_back
    try:
        # try to access the caller's "self"
        try:
            self_obj = frame.f_locals['self']
        except KeyError:
            return None

        # get the class of the "self" and return its name
        return type(self_obj).__name__
    finally:
        # make sure to clean up the frame at the end to avoid ref cycles
        del frame

The disadvantage of this is that it relies on the first parameter to be named "self". There are a few cases where we use different names, for example when writing a metaclass:

class MyMeta(type):
    def __call__(cls, *args, **kwargs):
        get_some_info()  # won't work!

And if you have a function with a self variable, it can produce unexpected results:

def not_a_method():
    self = 3
    print(get_some_info())  # output: int

We can solve both of these problems, but it takes a lot of work. We can inspect the name of the "self" parameter through the calling code object's co_varnames attribute. And in order to check whether the calling function is really a method defined in a class, we can loop through the self's MRO and try to find the method that called us. The end result is this monstrosity:

def get_some_info():
    # get the call frame of the calling method
    frame = inspect.currentframe().f_back
    try:
        # find the name of the first variable in the calling
        # function - which is hopefully the "self"
        codeobj = frame.f_code
        try:
            self_name = codeobj.co_varnames[0]
        except IndexError:
            return None

        # try to access the caller's "self"
        try:
            self_obj = frame.f_locals[self_name]
        except KeyError:
            return None

        # check if the calling function is really a method
        self_type = type(self_obj)
        func_name = codeobj.co_name

        # iterate through all classes in the MRO
        for cls in self_type.__mro__:
            # see if this class has a method with the name
            # we're looking for
            try:
                method = vars(cls)[func_name]
            except KeyError:
                continue

            # unwrap the method just in case there are any decorators
            try:
                method = inspect.unwrap(method)
            except ValueError:
                pass

            # see if this is the method that called us
            if getattr(method, '__code__', None) is codeobj:
                return self_type.__name__

        # if we didn't find a matching method, return None
        return None
    finally:
        # make sure to clean up the frame at the end to avoid ref cycles
        del frame

This should handle pretty much everything you throw at it correctly:

class Base:
    def my_method(whatever):
        print(get_some_info())

    @functools.lru_cache()  # could be any properly implemented decorator
    def my_decorated_method(foo):
        print(get_some_info())

    @classmethod
    def my_class_method(cls):
        print(get_some_info())

class A(Base):
    pass

def not_a_method(self=3):
    print(get_some_info())

A().my_method()            # prints "A"
A().my_decorated_method()  # prints "A"
A.my_class_method()        # prints "None"
not_a_method()             # prints "None"
print(get_some_info())     # prints "None"
Related Question