Get class that defined method
Asked Answered
S

13

122

How can I get the class that defined a method in Python?

I'd want the following example to print "__main__.FooClass":

class FooClass:
    def foo_method(self):
        print "foo"

class BarClass(FooClass):
    pass

bar = BarClass()
print get_class_that_defined_method(bar.foo_method)
Selfsatisfaction answered 7/6, 2009 at 2:18 Comment(2)
What version of Python are you using? Before 2.2 you could use im_class, but that was changed to show the type of the bound self object.Philpott
Good to know. But I'm using 2.6.Selfsatisfaction
G
80
import inspect

def get_class_that_defined_method(meth):
    for cls in inspect.getmro(meth.im_class):
        if meth.__name__ in cls.__dict__: 
            return cls
    return None
Giglio answered 7/6, 2009 at 2:23 Comment(8)
Beware, not all classes implement __dict__! Sometimes __slots__ is used. It's probably better to use getattrto test if the method is in the class.Foresail
@DeepYellow: _slots_ are likely not relevant to a method defined on a class, as they describe storage in memory on instances of that class. The methods directly implemented on a class should always be in its _dict_.Forsythia
For Python 3, please refer to this answer.Sprinkle
I am getting: 'function' object has no attribute 'im_class'Atiana
@Zitrax, it works in Python 2 -- for Python 3, follow the link in Yoel's comment just before yours.Giglio
In Python 2.7 it does not work. Same error about missing 'im_class'.Remediless
If meth is a classmethod, replace im_class with im_self. Works in Python 2.7.14. @AlexMartelli you might want to add it to the answer.Leucite
for those of use who are python 3 only -- is there anything wrong with using meth.__qualname__ ?Zsigmondy
T
14

I don't know why no one has ever brought this up or why the top answer has 50 upvotes when it is slow as hell, but you can also do the following:

def get_class_that_defined_method(meth):
    return meth.im_class.__name__

For python 3 I believe this changed and you'll need to look into .__qualname__.

Transcalent answered 2/10, 2017 at 21:9 Comment(3)
Hmm. I am not seeing the defining class when I do that in python 2.7 -- I am getting the class that the method was called on, not the one where it is defined...Adz
all the overly complex answers out there and this worked amazingly in python 3 - good workHypocorism
meth.__qualname__ gives you the qualified name of the method which includes the name of the class in which it is defined.Remuneration
W
9

In Python 3, if you need the actual class object you can do:

import sys
f = Foo.my_function
vars(sys.modules[f.__module__])[f.__qualname__.split('.')[0]]  # Gets Foo object

If the function could belong to a nested class you would need to iterate as follows:

f = Foo.Bar.my_function
vals = vars(sys.modules[f.__module__])
for attr in f.__qualname__.split('.')[:-1]:
    vals = vals[attr]
# vals is now the class Foo.Bar
Wellmannered answered 19/4, 2019 at 19:43 Comment(0)
S
8

Thanks Sr2222 for pointing out I was missing the point...

Here's the corrected approach which is just like Alex's but does not require to import anything. I don't think it's an improvement though, unless there's a huge hierarchy of inherited classes as this approach stops as soon as the defining class is found, instead of returning the whole inheritance as getmro does. As said, this is a very unlikely scenario.

def get_class_that_defined_method(method):
    method_name = method.__name__
    if method.__self__:    
        classes = [method.__self__.__class__]
    else:
        #unbound method
        classes = [method.im_class]
    while classes:
        c = classes.pop()
        if method_name in c.__dict__:
            return c
        else:
            classes = list(c.__bases__) + classes
    return None

And the Example:

>>> class A(object):
...     def test(self): pass
>>> class B(A): pass
>>> class C(B): pass
>>> class D(A):
...     def test(self): print 1
>>> class E(D,C): pass

>>> get_class_that_defined_method(A().test)
<class '__main__.A'>
>>> get_class_that_defined_method(A.test)
<class '__main__.A'>
>>> get_class_that_defined_method(B.test)
<class '__main__.A'>
>>> get_class_that_defined_method(C.test)
<class '__main__.A'>
>>> get_class_that_defined_method(D.test)
<class '__main__.D'>
>>> get_class_that_defined_method(E().test)
<class '__main__.D'>
>>> get_class_that_defined_method(E.test)
<class '__main__.D'>
>>> E().test()
1

Alex solution returns the same results. As long as Alex approach can be used, I would use it instead of this one.

Schoolman answered 29/11, 2012 at 14:38 Comment(6)
Cls().meth.__self__ just gives you the instance of Cls that is bound to that specific instance of meth. It's analogous to Cls().meth.im_class. If you have class SCls(Cls), SCls().meth.__self__ will get you a SCls instance, not a Cls instance. What the OP wants is to get Cls, which it appears is only available by walking the MRO as @Alex Martelli does.Suppression
@sr2222 You are right. I've modified the answer as I have already started though I think Alex solution is more compact.Schoolman
It's a good solution if you need to avoid imports, but since you are basically just re-implementing the MRO, it's not guaranteed to work forever. The MRO will probably stay the same, but it was already changed once in Python's past, and if it is changed again, this code will result is subtle, pervasive bugs.Suppression
Time and time again, "very unlikely scenarios" do happen in programming. Seldomly, causing disasters. In general, the thinking pattern "XY.Z% it won't ever happen" is extremely lousy thinking tool when doing coding. Don't use it. Write 100% correct code.Reverence
@Reverence I think you misread the explanation. It's not about correctness but speed. There is no "perfect" solution that fits all cases, otherwise e.g. there will be only one sorting algorithm. The solution proposed here should be faster in the "rare" case. Since speed comes in 99% percentage of all cases after readability, this solution might be a better solution in "only" that rare case. The code, in case you didn't read it, is 100% correct, if that was what you've feared.Schoolman
Warning, im_class is a Python 2 only attribute so this won't work with unbound methods in Python 3 (which are just regular functions).Businesswoman
D
2

I found __qualname__ is useful in Python3.

I test it like that:

class Cls(object):
     def func(self):
             print('1')

c = Cls()
print(c.func.__qualname__)
# output is: 'Cls.func'
def single_func():
     print(2)

print(single_func.__module__)
# output: '__main__'
print(single_func.__qualname__)
# output: 'single_func'

After my test, I found another answer here.

Dewhurst answered 18/11, 2022 at 3:40 Comment(0)
G
2

inspect._findclass seems to be working fine for any function/method.

import inspect
import sys


class SomeClass:
    @staticmethod
    def staticMethod():
        print('staticMethod')

    @classmethod
    def classMethod(cls):
        print('classMethod')

    def someMethod(self):
        print('bound method')

def myGlblFunc():
    print('Global function')


if __name__ == '__main__':
    static_method = SomeClass.staticMethod
    class_method = SomeClass.classMethod
    unbound_method = SomeClass.someMethod
    bound_method = SomeClass().someMethod
    glbl_func = myGlblFunc

    static_method()
    print(inspect._findclass(static_method), end='\n\n')

    class_method()
    print(inspect._findclass(class_method), end='\n\n')

    print('unbound method')
    print(inspect._findclass(unbound_method), end='\n\n')

    bound_method()
    print(inspect._findclass(bound_method), end='\n\n')

    glbl_func()
    print(inspect._findclass(glbl_func), end='\n\n')

    sys.exit(0)

# Output:
    # staticMethod
    # <class '__main__.SomeClass'>
    #
    # classMethod
    # <class '__main__.SomeClass'>
    #
    # unbound method
    # <class '__main__.SomeClass'>
    #
    # bound method
    # <class '__main__.SomeClass'>
    #
    # Global function
    # None
Guddle answered 16/6, 2023 at 12:21 Comment(1)
I am going with this solution. I returns the actual class object, not its str name, which is what I need. BUT this is a private module method so it feels flaky to use it. E.g. mypy complains it does not exist.Peatroy
P
1

I tried doing something similar to check if a stub method in a base class had been implemented or not in a subclass. Whichever way I tried I could not detect when an intermediate class was actually implementing the method (d.run_method() case below).

I finally did it by setting a method attribute and testing its presence later:

class A():
    def method(self):
        pass
    method._orig = None # This attribute will be gone once the method is implemented

    def run_method(self, *args, **kwargs):
        if hasattr(self.method, '_orig'):
            raise Exception('method not implemented')
        self.method(*args, **kwargs)

class B(A):
    pass

class C(B):
    def method(self):
        pass

class D(C):
    pass

B().run_method() # ==> Raises Exception: method not implemented
C().run_method() # OK
D().run_method() # OK

P.S.: This doesn't answer directly the question... IMHO there are two major reasons one would want to know which class defined a method; one is to point fingers at a class in debug code (such as in exception handling), and another is to determine if the method has been re-implemented (where method is a stub meant to be implemented by the programmer). This answer solves that second case in a different way.

Pro answered 15/10, 2014 at 20:44 Comment(1)
Nb: I probably haven't looked hard enough or maybe that was a Python2 limitation as I just did a pprint(self.method) on this code and it clearly prints the owning class, so the data is there somewhere.Pro
S
1

Python 3

Solved it in a very simple way:

str(bar.foo_method).split(" ", 3)[-2]

This gives

'FooClass.foo_method'

Split on the dot to get the class and the function name separately

Sthenic answered 17/9, 2020 at 8:7 Comment(1)
This can also be simplified to bar.foo_method.__qualname__ to get 'FooClass.foo_method. I don't know whether there are edge cases for that approach, but it does work for the question at hand.Paraffinic
P
1

Since Python 3.6, you have been able to use __set_name__ as a hook on a descriptor, as described in this answer to a duplicate of this question.

Pons answered 23/7, 2023 at 4:56 Comment(0)
S
0

if you get this error:

'function' object has no attribute 'im_class'

try this:

import inspect

def get_class_that_defined_method(meth):
    class_func_defided = meth.__globals__[meth.__qualname__.split('.')[0]]
    #full_func_name = "%s.%s.%s"%(class_func_defided.__module__,class_func_defided.__name__,meth.__name__)
    
    if inspect.isfunction(class_func_defided):
        print("%s is not part of a class."%meth.__name__)
        return None
    return class_func_defided

sample test:

class ExampleClass:
    @staticmethod
    def ex_static_method():
        print("hello from static method")
    
    def ex_instance_method(self):
        print("hello from instance method")

def ex_funct(self):
    print("hello from simple function")
    
if __name__ == "__main__":
    static_method_class = get_class_that_defined_method(ExampleClass.ex_static_method)
    static_method_class.ex_static_method()
    
    instance_method_class = get_class_that_defined_method(ExampleClass.ex_instance_method)
    instance_method_class().ex_instance_method()
    
    function_class = get_class_that_defined_method(ex_funct)
Subterfuge answered 7/2, 2023 at 22:10 Comment(0)
I
0

Another solution for Python 3:

class FooClass:
  def foo_method(self):
    print("foo")

class BarClass(FooClass):
  pass

class BazClass(BarClass):
  pass

baz = BazClass()

tmp = baz.foo_method.__self__.__class__
while hasattr(tmp.__base__, "foo_method"):
  tmp = tmp.__base__

print("defining class: {}".format(tmp))
tmp().foo_method()

Output:

defining class: <class '__main__.FooClass'>
foo

Python 2.7 or 3:

class FooClass:
  def foo_method(self):
    print("foo")

class BarClass(FooClass):
  pass

class BazClass(BarClass):
  pass

baz = BazClass()

tmp = baz.foo_method.__self__.__class__
while len(tmp.__bases__) > 0 and hasattr(tmp.__bases__[0], "foo_method"):
  tmp = tmp.__bases__[0]

print("defining class: {}".format(tmp))
tmp().foo_method()
Illusionary answered 11/4, 2023 at 11:59 Comment(0)
G
0

We can use method resolution order or mro() to find the name of SomeClass from some_method it has:

class SomeClass:
    def __init__(self):
        self.foo = 100
    
    def some_method(self):
        return self.foo

a = SomeClass()
print(a.some_method.__self__.__class__.mro()[0])

Outputs:

<class '__main__.SomeClass'>

that way we can find the name of the class that some_method belongs to even if it's inherited by SomeOtherClass:

class SomeClass:
    def __init__(self):
        self.foo = 100
    
    def some_method(self):
        return self.foo

class SomeOtherClass(SomeClass):
    def __init__(self):
        super().__init__()
        self.other_foo = 1
    
    def some_other_method(self):
        return self.other_foo

a = SomeOtherClass()
print([cls for cls in a.some_method.__self__.__class__.mro() if cls.__dict__.__contains__(a.some_method.__name__)][0])
print([cls for cls in a.some_other_method.__self__.__class__.mro() if cls.__dict__.__contains__(a.some_other_method.__name__)][0])

Outputs:

<class '__main__.SomeClass'>
<class '__main__.SomeOtherClass'>

or the names of all the classes that have some_method (or some_other_method):

print([cls for cls in a.some_method.__self__.__class__.mro() if hasattr(cls, a.some_method.__name__)])
print([cls for cls in a.some_other_method.__self__.__class__.mro() if hasattr(cls, a.some_other_method.__name__)])

Outputs:

[<class '__main__.SomeOtherClass'>, <class '__main__.SomeClass'>]
[<class '__main__.SomeOtherClass'>]

To get the __name__s in string:

print([cls.__name__ ...
Graecize answered 15/4, 2023 at 21:50 Comment(0)
W
0

simply use the __qualname__ attribute

ClassOrInstance.method.__qualname__ yields a Class.method string

Code, tested with Python 3.8.8

class Grandparent:
    def test(self):
            print("grandparent")

class Parent(Grandparent):
    def test(self):
        print("parent")

class Child(Parent):
    pass

class Uncle(Grandparent):
    pass
>>> Grandparent().test.__qualname__
'Grandparent.test'
>>> Parent().test.__qualname__
'Parent.test'
>>> Child().test.__qualname__
'Parent.test'
>>> Uncle().test.__qualname__
'Grandparent.test'

Next step

If you want to check for the place of implementation in the code, you can do

>>> Uncle.test.__qualname__.split(".")[0] == Grandparent.__name__
True
>>> Child.test.__qualname__.split(".")[0] == Grandparent.__name__
False
>>> Child.test.__qualname__.split(".")[0] == Parent.__name__
True

Here it is demonstrated for a class instead of an instance.

Witkin answered 21/4, 2023 at 9:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.