Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

get a class name of calling method

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

like image 783
Philipp Chapkovski Avatar asked Nov 05 '18 11:11

Philipp Chapkovski


People also ask

How can I get the class name that calls my method?

You can use StackFrame of System. Diagnostics and MethodBase of System. Reflection . StackFrame(Int32, Boolean) initializes a new instance of the StackFrame class that corresponds to a frame above the current stack frame, optionally capturing source information.

What is class name method?

Method Class | getName() Method in JavaMethod class is helpful to get the name of methods, as a String. To get name of all methods of a class, get all the methods of that class object. Then call getName() on those method objects. Syntax: public String getName()

What is calling method and called method?

Method CallsA method is a routine that applies to a particular class of objects. Once an object is declared, you can refer to it by its identifier when calling methods. You can call methods for a window that has not previously been declared using a special identifier for dynamic instantiation.


2 Answers

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"
like image 78
Aran-Fey Avatar answered Oct 10 '22 18:10

Aran-Fey


You could use inspect.stack():

def get_some_info():
    _stack = inspect.stack()[1]
    print ('cls:', _stack[0].f_locals['self'].__class__.__name__, 'func:', _stack[3])

....

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

Prints:

('cls:', 'A', 'func:', 'my_method')
('cls:', 'B', 'func:', 'my_method')
like image 22
Maurice Meyer Avatar answered Oct 10 '22 18:10

Maurice Meyer