11

I'm trying to find the name of the class that contains method code.

In the example underneath I use self.__class__.__name__, but of course this returns the name of the class of which self is an instance and not class that contains the test() method code. b.test() will print 'B' while I would like to get 'A'.

I looked into the inspect module documentation but did not find anything directly useful.

class A:
 def __init__(self):
 pass
 def test(self):
 print self.__class__.__name__
class B(A):
 def __init__(self):
 A.__init__(self)
a = A()
b = B()
a.test()
b.test()
SilentGhost
322k67 gold badges312 silver badges294 bronze badges
asked May 6, 2010 at 14:12
2
  • 1
    You know this at coding time, no? It's A: def test(self): print "A". Commented May 6, 2010 at 14:26
  • I want to avoid to replicate the class name. If I change the class name from A to AA I'll be the first to forget to adept the print "A" statement. Commented May 6, 2010 at 14:31

5 Answers 5

8

In Python 3.x, you can simply use __class__.__name__. The __class__ name is mildly magic, and not the same thing as the __class__ attribute of self.

In Python 2.x, there is no good way to get at that information. You can use stack inspection to get the code object, then walk the class hierarchy looking for the right method, but it's slow and tedious and will probably break when you don't want it to. You can also use a metaclass or a class decorator to post-process the class in some way, but both of those are rather intrusive approaches. And you can do something really ugly, like accessing self.__nonexistant_attribute, catching the AttributeError and extracting the class name from the mangled name. None of those approaches are really worth it if you just want to avoid typing the name twice; at least forgetting to update the name can be made a little more obvious by doing something like:

class C:
 ...
 def report_name(self):
 print C.__name__
answered May 6, 2010 at 14:38
Sign up to request clarification or add additional context in comments.

Comments

4

inspect.getmro gives you a tuple of the classes where the method might come from, in order. As soon as you find one of them that has the method's name in its dict, you're done:

for c in inspect.getmro(self.__class__):
 if 'test' in vars(c): break
return c.__name__
answered May 6, 2010 at 14:33

8 Comments

Unfortunately that gives you the name of the first class in the MRO that has an attribute with that name, which is not necessarily the same as the method you're doing this from (if your method is farther down the MRO.)
Nice solution, but as Thomas mentions it does not work when class B overrides the test() method: class A: def init__(self): pass def test(self): import inspect for c in inspect.getmro(self.__class): if 'test' in vars(c): break print c.__name__ class B(A): def __init__(self): A.__init__(self) def test(self): A.test(self) a = A() b = B() a.test() # prints A b.test() # prints B
sorry for the mess in previous comment. This is my first stackoverflow comment and I was not aware of the inability to add code in comments.
@Thomas, exactly. What I don't get is why this behavior is supposed to be wrong: it finds exactly the same class (defining test) as Python attribute access does.
Indeed it does, but that doesn't mean it's the answer the OP wanted :) The OP quite clearly asked for "the class this method is defined in", not "the most derived class that defines a method with this name".
|
2

Use __dict__ of class object itself:

class A(object):
 def foo(self):
 pass
class B(A):
 pass
def find_decl_class(cls, method):
 if method in cls.__dict__:
 return cls
 for b in cls.__bases__:
 decl = find_decl_class(b, method)
 if decl:
 return decl
print 'foo' in A.__dict__
print 'foo' in B.__dict__
print find_decl_class(B, 'foo').__name__

Will print True, False, A

answered May 6, 2010 at 14:40

2 Comments

This has the same problem as Alex's MRO solution.
Agree. But if it is known in advance that there will be just single-class inheritance, solution is OK and simple. Thanks for the note.
2

You can use (abuse?) private name mangling to accomplish this effect. If you look up an attribute on self that starts with __ from inside a method, python changes the name from __attribute to _classThisMethodWasDefinedIn__attribute.

Just somehow stash the classname you want in mangled-form where the method can see it. As an example, we can define a __new__ method on the base class that does it:

def mangle(cls, attrname):
 if not attrname.startswith('__'):
 raise ValueError('attrname must start with __')
 return '_%s%s' % (cls.__name__, attrname)
class A(object):
 def __new__(cls, *args, **kwargs):
 obj = object.__new__(cls)
 for c in cls.mro():
 setattr(obj, mangle(c, '__defn_classname'), c.__name__)
 return obj
 def __init__(self):
 pass
 def test(self):
 print self.__defn_classname
class B(A):
 def __init__(self):
 A.__init__(self)
a = A()
b = B()
a.test()
b.test()

which prints:

A
A
answered May 6, 2010 at 15:33

Comments

0

You can do

>>> class A(object):
... def __init__(self):
... pass
... def test(self):
... for b in self.__class__.__bases__:
... if hasattr(b, 'test'):
... return b.__name__
... return self.__class__.__name__
... 
>>> class B(A):
... def __init__(self):
... A.__init__(self)
...
>>> B().test()
'A'
>>> A().test()
'A'
>>> 

Keep in mind that you could simplify it by using __class__.__base__, but if you use multiple inheritance, this version will work better.

It simply checks first on its baseclasses for test. It's not the prettiest, but it works.

answered May 6, 2010 at 16:33

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.