Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find class in which a method is defined

Tags:

python

I want to figure out the type of the class in which a certain method is defined (in essence, the enclosing static scope of the method), from within the method itself, and without specifying it explicitly, e.g.

class SomeClass:
    def do_it(self):
        cls = enclosing_class() # <-- I need this.
        print(cls)

class DerivedClass(SomeClass):
    pass

obj = DerivedClass()
# I want this to print 'SomeClass'.
obj.do_it()

Is this possible?

like image 204
reddish Avatar asked Sep 09 '14 15:09

reddish


1 Answers

If you need this in Python 3.x, please see my other answer—the closure cell __class__ is all you need.


If you need to do this in CPython 2.6-2.7, RickyA's answer is close, but it doesn't work, because it relies on the fact that this method is not overriding any other method of the same name. Try adding a Foo.do_it method in his answer, and it will print out Foo, not SomeClass

The way to solve that is to find the method whose code object is identical to the current frame's code object:

def do_it(self):
    mro = inspect.getmro(self.__class__)
    method_code = inspect.currentframe().f_code
    method_name = method_code.co_name
    for base in reversed(mro):
        try:
            if getattr(base, method_name).func_code is method_code:
                print(base.__name__)
                break
        except AttributeError:
            pass

(Note that the AttributeError could be raised either by base not having something named do_it, or by base having something named do_it that isn't a function, and therefore doesn't have a func_code. But we don't care which; either way, base is not the match we're looking for.)

This may work in other Python 2.6+ implementations. Python does not require frame objects to exist, and if they don't, inspect.currentframe() will return None. And I'm pretty sure it doesn't require code objects to exist either, which means func_code could be None.

Meanwhile, if you want to use this in both 2.7+ and 3.0+, change that func_code to __code__, but that will break compatibility with earlier 2.x.


If you need CPython 2.5 or earlier, you can just replace the inpsect calls with the implementation-specific CPython attributes:

def do_it(self):
    mro = self.__class__.mro()
    method_code = sys._getframe().f_code
    method_name = method_code.co_name
    for base in reversed(mro):
        try:
            if getattr(base, method_name).func_code is method_code:
                print(base.__name__)
                break
        except AttributeError:
            pass

Note that this use of mro() will not work on classic classes; if you really want to handle those (which you really shouldn't want to…), you'll have to write your own mro function that just walks the hierarchy old-school… or just copy it from the 2.6 inspect source.

This will only work in Python 2.x implementations that bend over backward to be CPython-compatible… but that includes at least PyPy. inspect should be more portable, but then if an implementation is going to define frame and code objects with the same attributes as CPython's so it can support all of inspect, there's not much good reason not to make them attributes and provide sys._getframe in the first place…

like image 108
abarnert Avatar answered Sep 19 '22 14:09

abarnert