47

My objective is to stimulate a sequence diagram of an application for this I need the information about a caller and callee class names at runtime. I can successfully retrieve the caller function but not able to get a caller class name?

#Scenario caller.py:

import inspect

class A:

    def Apple(self):
        print "Hello"
        b=B()
        b.Bad()



class B:

    def Bad(self):
        print"dude"
        print inspect.stack()


a=A()
a.Apple()

When I printed the stack there was no information about the caller class. So is it possible to retrieve the caller class during runtime ?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kaushik
  • 1,264
  • 8
  • 20
  • 32
  • What do you mean by "caller class". Do you mean `a`, `A`, `b` or `B`? – mgilson Jun 12 '13 at 12:18
  • @mgilson What i meant is when the code is running in the method "def Bad" which is under the class B (callee) i must be able to print retrieve the name of the caller class which is "A" in this case. – Kaushik Jun 12 '13 at 12:21
  • @mgilson I can print "inspect.stack()[1][3]" statement which would get me only the caller function. – Kaushik Jun 12 '13 at 12:22
  • Similar, related, interesting to see: [get a class name of calling method](https://stackoverflow.com/questions/53153075/get-a-class-name-of-calling-method/53153512) – artu-hnrq Dec 17 '21 at 03:43

6 Answers6

63

Well, after some digging at the prompt, here's what I get:

stack = inspect.stack()
the_class = stack[1][0].f_locals["self"].__class__.__name__
the_method = stack[1][0].f_code.co_name

print("I was called by {}.{}()".format(the_class, the_method))
# => I was called by A.a()

When invoked:

➤ python test.py
A.a()
B.b()
  I was called by A.a()

given the file test.py:

import inspect

class A:
  def a(self):
    print("A.a()")
    B().b()

class B:
  def b(self):
    print("B.b()")
    stack = inspect.stack()
    the_class = stack[1][0].f_locals["self"].__class__.__name__
    the_method = stack[1][0].f_code.co_name
    print("  I was called by {}.{}()".format(the_class, the_method))

A().a()

Not sure how it will behave when called from something other than an object.

M. Gruber
  • 351
  • 3
  • 10
brice
  • 24,329
  • 7
  • 79
  • 95
  • 1
    Nicely done. At some point I'd like to become more familiar with code and frame objects... – mgilson Jun 12 '13 at 12:43
  • 4
    Note that this won't work for a static or classmethod either as `self` then won't be defined, or it won't be an instance of the class (most likely) – mgilson Jun 12 '13 at 12:47
  • @mgilson: can you suggest a better way?' – Swadhikar Mar 05 '17 at 07:07
  • 2
    This work of the method is invoked via `super` in a subclass. Or at least it won't be what you expect since `type(self)` will be the type of the subclass, not the the type of the class which actually owns the method. – rmorshea Jun 10 '17 at 20:53
  • stack[1][0].f_locals["self"].__class__ returns type instead of str. For getting class name in string a cast is needed the_class = str(stack[1][0].f_locals["self"].__class__) – Code_Worm Jan 25 '19 at 21:17
  • 2
    A more accurate way to get the caller class is stack[1][0].f_locals["__class\_\_"]. The calling class is returned, not the class of the first argument. – Elijah May 20 '20 at 16:59
  • 2
    This was extremely useful to me. I just wanted to give a sincere thank-you. – duma Oct 08 '21 at 13:50
8

Using the answer from Python: How to retrieve class information from a 'frame' object?

I get something like this...

import inspect

def get_class_from_frame(fr):
  args, _, _, value_dict = inspect.getargvalues(fr)
  # we check the first parameter for the frame function is
  # named 'self'
  if len(args) and args[0] == 'self':
    # in that case, 'self' will be referenced in value_dict
    instance = value_dict.get('self', None)
    if instance:
      # return its class
      return getattr(instance, '__class__', None)
  # return None otherwise
  return None


class A(object):

    def Apple(self):
        print "Hello"
        b=B()
        b.Bad()

class B(object):

    def Bad(self):
        print"dude"
        frame = inspect.stack()[1][0]
        print get_class_from_frame(frame)


a=A()
a.Apple()

which gives me the following output:

Hello
dude
<class '__main__.A'>

clearly this returns a reference to the class itself. If you want the name of the class, you can get that from the __name__ attribute.

Unfortunately, this won't work for class or static methods ...

Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • This method is actually more perfect since it checks for "self" and if not displays "None". This is exactly what I needed. – Kaushik Jun 12 '13 at 12:59
6

Instead of indexing the return value of inspect.stack(), one could use the method inspect.currentframe(), which avoids the indexing.

prev_frame = inspect.currentframe().f_back
the_class = prev_frame.f_locals["self"].__class__
the_method = prev_frame.f_code.co_name
djeongo
  • 61
  • 1
  • 1
  • 1
    This would print something like `I was called by .your_method`. To achieve the same result from accepted answer, change the variable `the_class` to `prev_frame.f_locals['self'].__class__.__name__` – igorkf Apr 08 '21 at 22:51
5

Perhaps this is breaking some Python programming protocol, but if Bad is always going to check the class of the caller, why not pass the caller's __class__ to it as part of the call?

class A:

    def Apple(self):
        print "Hello"
        b=B()
        b.Bad(self.__class__)



class B:

    def Bad(self, cls):
        print "dude"
        print "Calling class:", cls


a=A()
a.Apple()

Result:

Hello
dude
Calling class: __main__.A

If this is bad form, and using inspect truly is the preferred way to get the caller's class, please explain why. I'm still learning about deeper Python concepts.

Justin S Barrett
  • 636
  • 4
  • 14
1

Python 3.8

import inspect


class B:
    def __init__(self):
        if (parent := inspect.stack()[1][0].f_locals.get('self', None)) and isinstance(parent, A):
            parent.print_coin()


class A:
    def __init__(self, coin):
        self.coin: str = coin
        B()

    def print_coin(self):
        print(f'Coin name: {self.coin}')


A('Bitcoin')
  • 1
    Never thought calling a method or accessing attributes from the caller's class was possible. This provides interesting flexibility. – JWCompDev Feb 19 '22 at 07:18
  • @jlwise24 You are right, it is very interesting, but sometimes it is slow. You have to be careful, especially in Python. Read about "introspection and reflection" in programming. – Джон Смит Feb 19 '22 at 17:10
0

To store class instance name from the stack to class variable:

import inspect

class myClass():

    caller = ""

    def __init__(self):
        s = str(inspect.stack()[1][4]).split()[0][2:]
        self.caller = s

    def getInstanceName(self):
        return self.caller

This

myClassInstance1 = myClass()
print(myClassInstance1.getInstanceName())

will print:

myClassInstance1
Mika72
  • 413
  • 2
  • 12