zegkljan
zegkljan

Reputation: 8391

How to get the defining class from a method body?

I have two classes:

class A:
    def foo(self):
        print(<get the defining class>)

class B(A):
    pass

Now, I need to replace <get the defining class> by a code such that this:

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

produces this (i.e. this is the expected behaviour):

A
A

I tried self.__class__.__name__ but that obviously produces B for the last call as the self is, in fact, of class B.

So the ultimate question is: if I'm in a method body (which is not a class method), how can I get the name of the class the method is defined in?

Upvotes: 0

Views: 61

Answers (2)

Curtis Lusmore
Curtis Lusmore

Reputation: 1872

I'm assuming that you don't want to hard-code this logic into each function, since you could do so trivially in your example code.

If the print statement is always the first statement of the methods for which you want this behaviour, one possible solution would be to use a decorator.

def print_defining_class(fn):
    calling_class = fn.__qualname__.split('.')[0]
    def decorated(*args, **kwargs):
        print(calling_class)
        return fn(*args, **kwargs)
    return decorated

class A:
    @print_defining_class
    def method(self): pass

class B(A): pass

A().method() # A
B().method() # A

Upvotes: -1

Dimitris Fasarakis Hilliard
Dimitris Fasarakis Hilliard

Reputation: 160377

The simplest way to do this is by using the functions qualified name:

class A:
    def foo(self):
        print(self.foo.__qualname__[0])

class B(A):
    pass

The qualified name consists of the class defined and the function name in cls_name.func_name form. __qualname__[0] suits you here because the class name consists of a single character; it's better of course to split on the dot and return the first element self.foo.__qualname__.split('.')[0].

For both, the result is:

>>> a = A()
>>> b = B()
>>> a.foo()
A
>>> b.foo()
A

A more robust approach is climbing the __mro__ and looking inside the __dict__s of every class for a function type(self).foo:

def foo(self):
    c = [cls.__name__ for cls in type(self).__mro__ if getattr(type(self), 'foo', None) in cls.__dict__.values()]
    print(c[0])

This is a bit more complex but yields the same result.

Upvotes: 2

Related Questions