5

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?

reddish
  • 1,360
  • 2
  • 12
  • 27
  • 3
    `enclosing_class = lambda: SomeClass` ;-) – mgilson Sep 09 '14 at 15:14
  • You'll probably just have to search the `mro`. – roippi Sep 09 '14 at 15:15
  • In my question, I stated 'without specifying it explicitly'. – reddish Sep 09 '14 at 15:15
  • 2
    @reddish -- But you didn't state _why_. This smells like an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – mgilson Sep 09 '14 at 15:28
  • 4
    @reddish -- Sure it isn't, but I'm really _am_ trying to be helpful. But, what you're asking for is a very difficult thing to achieve. It probably boils down to inspecting stack frames and code objects -- possibly walking the MRO and inspecting the source code for every method defined there until you find what you're looking for. If you would let us know what utility there is in such a function, we might be able to point you to a much easier/neater/cleaner alternative to accomplish the same goal. – mgilson Sep 09 '14 at 16:12
  • http://stackoverflow.com/questions/1401661/python-list-all-base-classes-in-a-hierarchy – RickyA Sep 09 '14 at 16:46
  • @RickA - that addresses a different question. – reddish Sep 09 '14 at 17:14
  • @shx2: that's close to what I need. However, how do you obtain a reference to the method you are currently executing by introspection (without spelling it out, which destroys the purpose)? – reddish Sep 09 '14 at 17:17
  • 1
    Which version of Python are you asking for? Because, while this is almost certainly a very bad thing to do, there are ways to do it for at least some implementations and versions, but they're not necessarily the same, and I don't want to answer for every possible version ever… – abarnert Sep 09 '14 at 17:46
  • Actually, it looks like shx2 is right and this is a dup… although that question needs a 3.x answer. – abarnert Sep 09 '14 at 18:17
  • Never mind, I was wrong, http://stackoverflow.com/questions/22898037/how-to-check-in-python-from-which-class-methods-is-derived/ asks how to get it from _outside_ instead of from _inside. – abarnert Sep 09 '14 at 18:20
  • After seeing your explanation (you want to inject instrumentation into methods dynamically), then @mgilson was exactly right: there is a much easier/neater/cleaner alternative. When you're injecting that instrumentation, you have know the classes and methods you're injecting it into, so you can just inject it statically instead of trying to recover it dynamically. And this is exactly why XY questions are a problem—no matter how convinced you are that Y is the only way to do it, there's _always_ a good chance you're wrong, and it's always helpful to hear alternative suggestions. – abarnert Sep 09 '14 at 18:54
  • @abarnert: 'there is a much easier/neater/cleaner alternative' - I explained the tradeoff elsewhere. I don't want to have to type the class names; that would invite mistakes. As to 'this is exactly why XY questions are a problem': don't you think that the question is interesting in and of itself? Who cares why I need it? – reddish Sep 09 '14 at 18:59
  • @reddish: And you're wrong about the tradeoff; you don't _have_ to type the class names. And the fact that you have to type the same N lines of code everywhere is a much bigger problem that you need to fix anyway. And the reason I care that you need it is that you actually don't need it, and most likely 99% of the people looking for this answer won't need it either, and as long as you believe it's in everyone's interest that people write better code, it's better that they don't write this code. (And if you don't believe that matters, SO is pretty much pointless.) – abarnert Sep 09 '14 at 19:17

6 Answers6

5

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…

abarnert
  • 354,177
  • 51
  • 601
  • 671
3

First, this is almost certainly a bad idea, and not the way you want to solve whatever you're trying to solve but refuse to tell us about…

That being said, there is a very easy way to do it, at least in Python 3.0+. (If you need 2.x, see my other answer.)

Notice that Python 3.x's super pretty much has to be able to do this somehow. How else could super() mean super(THISCLASS, self), where that THISCLASS is exactly what you're asking for?*

Now, there are lots of ways that super could be implemented… but PEP 3135 spells out a specification for how to implement it:

Every function will have a cell named __class__ that contains the class object that the function is defined in.

This isn't part of the Python reference docs, so some other Python 3.x implementation could do it a different way… but at least as of 3.2+, they still have to have __class__ on functions, because Creating the class object explicitly says:

This class object is the one that will be referenced by the zero-argument form of super(). __class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. This allows the zero argument form of super() to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method.

(And, needless to say, this is exactly how at least CPython 3.0-3.5 and PyPy3 2.0-2.1 implement super anyway.)

In [1]: class C:
   ...:     def f(self):
   ...:         print(__class__)
In [2]: class D(C):
   ...:     pass
In [3]: D().f()
<class '__main__.C'>

Of course this gets the actual class object, not the name of the class, which is apparently what you were after. But that's easy; you just need to decide whether you mean __class__.__name__ or __class__.__qualname__ (in this simple case they're identical) and print that.


* In fact, this was one of the arguments against it: that the only plausible way to do this without changing the language syntax was to add a new closure cell to every function, or to require some horrible frame hacks which may not even be doable in other implementations of Python. You can't just use compiler magic, because there's no way the compiler can tell that some arbitrary expression will evaluate to the super function at runtime…

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Thanks for your detailed answer. This is indeed a clean solution to the question I posed. For my specific situation it would be more convenient if I could reach the enclosing class from within an actual 'enclosing_scope()' function, but this solution goes 95% towards what I need. – reddish Sep 09 '14 at 18:46
  • As to the background of my question: this is about adding instrumentation to a code base to be able to generate reports of method invocation counts, for the purpose of checking certain approximate runtime invariants (e.g. "the number of times that method ClassA.x() is executed is approximately equal to the number of times that method ClassB.y() is executed in the course of a run of a complicated program). But regardless of that particular use case, I think the question as posed is interesting enough to warrant an answer, which is why I prefer to stay away from the background story. – reddish Sep 09 '14 at 18:47
  • @reddish: But it sounds like there's a much better way to solve this: at the time you're instrumenting the classes, you know the classes you're instrumenting, and therefore you don't need to recover that information dynamically in the first place. (Notice that this is the exact same reason the compiler adds `__class__` at compile time: so it doesn't have to be recovered dynamically when you call `super`.) – abarnert Sep 09 '14 at 18:50
  • yes, but I will have to instrument many classes by hand, and to prevent mistakes I want to avoid typing the class names everywhere. In essence, it's the same reason why typing super() is preferable to typing super(ClassX, self). – reddish Sep 09 '14 at 18:55
  • @reddish: And that's exactly why you should write a function to do the instrumentation for you instead of doing it by hand. (And the fact that you're doing it statically rather than dynamically just means the function should probably be a decorator, not that it shouldn't exist.) – abarnert Sep 09 '14 at 19:00
  • Great, we are now having the discussion I wanted to avoid. Writing a decorator isn't an option, because that clashes with the PySide decorators I am forced to use. PySide does funky introspection stuff that makes it impossible to reliably decorate @QtCore.Slot() decorated methods. – reddish Sep 09 '14 at 19:05
  • @reddish: I'm 99% sure that you _can_ use other decorators with `Slot`, you just have to either (a) carefully use them in the right order, or (b) pass some additional attributes (which maybe `PySide` should document better—or, better, provide for you as a constant…) to `@functools.wraps`. – abarnert Sep 09 '14 at 19:20
1

If you can use @abarnert's method, do it.

Otherwise, you can use some hardcore introspection (for python2.7):

import inspect
from http://stackoverflow.com/a/22898743/2096752 import getMethodClass

def enclosing_class():
    frame = inspect.currentframe().f_back
    caller_self = frame.f_locals['self']
    caller_method_name = frame.f_code.co_name
    return getMethodClass(caller_self.__class__, caller_method_name)

class SomeClass:
    def do_it(self):
        print(enclosing_class())

class DerivedClass(SomeClass):
    pass

DerivedClass().do_it() # prints 'SomeClass'

Obviously, this is likely to raise an error if:

  • called from a regular function / staticmethod / classmethod
  • the calling function has a different name for self (as aptly pointed out by @abarnert, this can be solved by using frame.f_code.co_varnames[0])
shx2
  • 61,779
  • 13
  • 130
  • 153
  • 1
    You can solve the last problem, at least in CPython and PyPy, by using `f.f_code.co_varnames[0]` instead of `self`. Or, maybe better (but I forget which version it was added) `inspect.getargs(f.f_code).args[0]`. – abarnert Sep 09 '14 at 18:03
  • After checking the docs, `getargs` is not documented, it's just part of the internals used for implementing the documented functions, and it doesn't exist in (at least) CPython 2.6-2.7, so… it doesn't help here (if you've got Python 3.x, just use `__class__`), so I guess you have to go with `co_varnames`. – abarnert Sep 09 '14 at 18:12
1

Sorry for writing yet another answer, but here's how to do what you actually want to do, rather than what you asked for:

this is about adding instrumentation to a code base to be able to generate reports of method invocation counts, for the purpose of checking certain approximate runtime invariants (e.g. "the number of times that method ClassA.x() is executed is approximately equal to the number of times that method ClassB.y() is executed in the course of a run of a complicated program).

The way to do that is to make your instrumentation function inject the information statically. After all, it has to know the class and method it's injecting code into.

I will have to instrument many classes by hand, and to prevent mistakes I want to avoid typing the class names everywhere. In essence, it's the same reason why typing super() is preferable to typing super(ClassX, self).

If your instrumentation function is "do it manually", the very first thing you want to turn it into an actual function instead of doing it manually. Since you obviously only need static injection, using a decorator, either on the class (if you want to instrument every method) or on each method (if you don't) would make this nice and readable. (Or, if you want to instrument every method of every class, you might want to define a metaclass and have your root classes use it, instead of decorating every class.)

For example, here's an easy way to instrument every method of a class:

import collections
import functools
import inspect

_calls = {}
def inject(cls):
    cls._calls = collections.Counter()
    _calls[cls.__name__] = cls._calls
    for name, method in cls.__dict__.items():
        if inspect.isfunction(method):
            @functools.wraps(method)
            def wrapper(*args, **kwargs):
                cls._calls[name] += 1
                return method(*args, **kwargs)
            setattr(cls, name, wrapper)
    return cls

@inject
class A(object):
    def f(self):
        print('A.f here')

@inject
class B(A):
    def f(self):
        print('B.f here')

@inject
class C(B):
    pass

@inject
class D(C):
    def f(self):
        print('D.f here')

d = D()
d.f()
B.f(d)

print(_calls)

The output:

{'A': Counter(), 
 'C': Counter(), 
 'B': Counter({'f': 1}), 
 'D': Counter({'f': 1})}

Exactly what you wanted, right?

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Not quite, but it is an interesting approach. It does make me yearn for days gone when Python was actually a simple language. – reddish Sep 09 '14 at 19:44
  • @reddish: I've been using Python since 1.5. The last changes I can think of that really made the language more complex are descriptors (2.2) and generators (2.3). And since then, there have definitely been things that made it simpler (no more `int`/`long`, two kinds of classes, `cmp` vs. rich comparisons, stdlib modules that don't quite work with Unicode but don't give errors, …), although of course most of those happened all at once in 3.0. – abarnert Sep 09 '14 at 20:03
  • What I perhaps mean to say is that it takes quite a bit of effort and background to understand how your proposed code works; which contrasts starkly to the basic idea of Python as a language to write easily readable code in (the 'executable pseudocode' idea). The rather complex scaffolding you need to reach the clean decorator syntax makes me uncomfortable. But I appreciate the end result. – reddish Sep 09 '14 at 20:16
  • 1
    @reddish: I really don't see what's so complicated here. You're trying to dynamically modify all of the methods of a class; there's a limit to how easy that could be—or even _should_ be. More importantly, the way I do it—by monkeypatching the class—is exactly the same way I would have done it in Python 1.6. The only difference is that I can use `inspect` and `functools` methods instead of accessing and setting quasi-undocumented attributes, and I can wrap it in a decorator so I don't have to write `C = inspect(C)` for each class. (And unlike 2.2-2.7, I don't have to deal with bound methods.) – abarnert Sep 09 '14 at 22:26
  • 1
    @reddish: Unless your argument is that "no one would have tried to do this in Python 1.6, therefore it wouldn't have mattered that it would have been a bit complicated"… in which case the answer isn't that Python has gotten more complex, but that your needs and demands have, because you've learned some powerful ideas in the mean time (like aspects) that aren't built into Python and have to be built manually. – abarnert Sep 09 '14 at 22:28
0

You can either do what @mgilson suggested or take another approach.

class SomeClass:
    pass

class DerivedClass(SomeClass):
    pass

This makes SomeClass the base class for DerivedClass.
When you normally try to get the __class__.name__ then it will refer to derived class rather than the parent.

When you call do_it(), it's really passing DerivedClass as self, which is why you are most likely getting DerivedClass being printed.

Instead, try this:

class SomeClass:
    pass

class DerivedClass(SomeClass):
    def do_it(self):
        for base in self.__class__.__bases__:
            print base.__name__
obj = DerivedClass()
obj.do_it() # Prints SomeClass

Edit:
After reading your question a few more times I think I understand what you want.

class SomeClass:
    def do_it(self):
        cls = self.__class__.__bases__[0].__name__
        print cls

class DerivedClass(SomeClass):
    pass

obj = DerivedClass()
obj.do_it() # prints SomeClass
Chrispresso
  • 3,660
  • 2
  • 19
  • 31
  • I understand all this. Your proposed solution does not address my question. – reddish Sep 09 '14 at 16:41
  • @reddish, I edited my answer for what I think you want. Let me know if it works as you intended – Chrispresso Sep 09 '14 at 16:50
  • This approach doesn't do what I need. Your approach presupposes that I call 'do_it' from a class that is one inheritance level below SomeClass. If I call 'do_it' from a direct SomeClass instance, or from an instance of a class that derives from 'DerivedClass', it will break. – reddish Sep 09 '14 at 17:06
0

[Edited] A somewhat more generic solution:

import inspect

class Foo:
    pass

class SomeClass(Foo):
    def do_it(self):
        mro = inspect.getmro(self.__class__)
        method_name = inspect.currentframe().f_code.co_name
        for base in reversed(mro):
            if hasattr(base, method_name):
                print(base.__name__)
                break

class DerivedClass(SomeClass):
    pass

class DerivedClass2(DerivedClass):
    pass

DerivedClass().do_it()
>> 'SomeClass'

DerivedClass2().do_it()
>> 'SomeClass'

SomeClass().do_it()
>> 'SomeClass'

This fails when some other class in the stack has attribute "do_it", since this is the signal name for stop walking the mro.

RickyA
  • 15,465
  • 5
  • 71
  • 95
  • Doesn't this just print the first base class name? It won't work if `SomeClass` is derived from another class. – interjay Sep 09 '14 at 17:02
  • Yes, it prints the first class in the inheritance stack, but OP is not specifying on what position his class has in the stack, and in his example it is the first... – RickyA Sep 09 '14 at 17:07
  • I specified that I want the 'enclosing static scope of the method'. Your approach doesn't do that. – reddish Sep 09 '14 at 17:08
  • Updated to also include baseclasses. – RickyA Sep 09 '14 at 17:42
  • I'm not sure why the other linked answer and this one both reverse the MRO. You need to search it in the order given, to find the last latest definition of the function, no? – Ned Batchelder Sep 09 '14 at 17:54
  • you want it reversed because `DerivedClass` and `DerivedClass2` also both report `True` on `hasattr(base, method_name)`. So then you would print `DerivedClass` and `DerivedClass2` instead of 'SomeClass`. – RickyA Sep 09 '14 at 17:57
  • @reddish The enclosing static scope of a method is NOT the class; it is the nonce module created during class initialisation. It sounds like you are trying to do something which isn't really possible in python, probably because you are trying to solve some other problem in the wrong way. – Marcin Sep 09 '14 at 18:06
  • @abarnert: Nope. `DerivedClass().do_it()` that prints `__class__` outputs `DerivedClass`, not `SomeClass` – RickyA Sep 09 '14 at 18:06
  • @abarnert `self.__class__` is NOT guaranteed to be class where `do_it` is defined. It could be any subclass thereof. – Marcin Sep 09 '14 at 18:06
  • @abarnert Re the namespace: you're misreading that section. It says what I said in my comment. The class namespace is not the same thing as the class. – Marcin Sep 09 '14 at 18:08
  • @abarnert I don't know what you mean by http://stackoverflow.com/questions/25748075/find-class-in-which-a-method-is-defined#comment40265900_25750132. `self.__class__` is the class of `self`, not the class where the method was defined. – Marcin Sep 09 '14 at 18:12
  • @abarnert I think we might be talking at cross purposes. I'm talking about `self.__class__`. Note, however, that python 2 has new-style classes after 2.2 (with some tweaks in 2.3). – Marcin Sep 09 '14 at 18:23
  • @Marcin: Yes, but if you define `class Foo:` with no base classes, you don't get a new-style class in 2.x. That's why I assume your code is meant for 3.x only. In which case you really don't need to do any of this. – abarnert Sep 09 '14 at 18:25
  • @abarnert I have no idea what you mean by "my code". Also, a new-style class is only required to have a metaclass. – Marcin Sep 09 '14 at 18:26
  • @Marcin: Sorry, I meant RickyA's code; you were saying the exact same thing as him and I mixed you up. – abarnert Sep 09 '14 at 18:27
  • And I am on 2.7, not 3.x, and so is (I think) OP. Although he is very quiet now, so we cant tell for sure. – RickyA Sep 09 '14 at 18:29
  • @RickyA: If you're on 2.7, why are you defining classic classes? And why are you using `print` as if it were a function? – abarnert Sep 09 '14 at 18:30
  • Anyway, this still doesn't work if `Foo` also defined a method named `do_it`. It has the exact same problem that you pointed out to Ned about searching the mro in forward order. I think I have a solution for that, but let me test it… – abarnert Sep 09 '14 at 18:31
  • @abarnert I use classics because OP uses them and `print()` is 3.x syntax so better to get used to that now. – RickyA Sep 09 '14 at 18:32
  • @abarnert well, the easy solution for the methodname is to name it `find-class-in-which-a-method-is-defined():`; that won't possibly collide in mro ;) – RickyA Sep 09 '14 at 18:34
  • @RickyA: If you did that, this whole question wouldn't arise in the first place. – abarnert Sep 09 '14 at 18:35
  • @abarnert: and why would that be? We don't know OPs purpose here; he quite secretive (and silent now) – RickyA Sep 09 '14 at 18:37
  • @RickyA: First, you're assuming that the only reason he wants the class is to print it out, rather than to actually use it for something in a real method. Second, he said in comments that he can't control or make any use of the inheritance hierarchy, which is why he has to do it dynamically. But, most important of all: if you want to put the exact same method in both `Foo` and `SomeClass`, any reasonable name you come up with will be the same name in both cases, and you want them to print out `Foo` and `SomeClass`, not `Foo` and `Foo`. – abarnert Sep 09 '14 at 18:47