tags:

views:

101

answers:

3

Trying to convert super(B, self).method() into a simple nice bubble() call. Did it, see below!

Is it possible to get reference to class B in this example?

class A(object): pass

class B(A):
    def test(self):
        test2()

class C(B): pass

import inspect
def test2():
    frame = inspect.currentframe().f_back
    cls = frame.[?something here?]
    # cls here should == B (class)

c = C()
c.test()

Basically, C is child of B, B is child of A. Then we create c of type C. Then the call to c.test() actually calls B.test() (via inheritance), which calls to test2().

test2() can get the parent frame frame; code reference to method via frame.f_code; self via frame.f_locals['self']; but type(frame.f_locals['self']) is C (of course), but not B, where method is defined.

Any way to get B?

A: 

Since functions in Python are not bound to classes [1], general answer is "not possible". However, for non-static functions that follow standard naming convention for attributes you could probably try

inspect.currentframe ().f_back.f_locals['self'].class

[1] for instance, you could do:

class X: pass
X.foo = (lambda self: None)

EDIT:

I managed to suggest a way already present in the question :-/. However, I'm still curious how you managed to do that, as I'm still certain functions are not bound to classes in Python.

doublep
Actually possible, I'll publish it in a second. (Not needed for static methods, since I was searching for a replacement for super(), which is run only on bound methods)
Slava N
@doublep: 1. Try evaluating `X.foo.im_class` and you will be surprised. 2. The solution you propose appears in the question.
interjay
@interjay: Oh damn, indeed. That's what I get for not reading to the end :(. However, `X.foo.im_class` won't help in this case. Try `Y.foo = X.foo`; the function will be the same, but `im_class` not: that's a property of method binding object, not the function.
doublep
@doublep: C.foo = B.foo is indeed a problem for my posted solution... but for most cases where I want it - it'll work.
Slava N
@doublep BTW `inspect.currentframe ().f_back.f_locals['self'].__class__` won't work, since it'll return `C` instead of `B`.
Slava N
@Slava: Yes, you are right.
doublep
Methods are not bound to classes, but they *are* bound to instances. Try this (adding newlines where needed): `class myclass: def f(self): pass` `x = myclass()` `print x.f`
Daniel Stutzbach
@Daniel: Yes, but that's not required. You can call `f()` like this too: `X.__dict__['f'] (x)` and here nothing is bound to either class `X` or instance `x`. I.e. what is bound in your case is method binding object, but this object is not required to invoke the method.
doublep
A: 

Found a shorter way to do super(B, self).test() -> bubble() from below.

(Works with multiple inheritance, doesn't require arguments, correcly behaves with sub-classes)

The solution was to use inspect.getmro(type(back_self)) (where back_self is a self from callee), then iterating it as cls with method_name in cls.__dict__ and verifying that the code reference we have is the one in this class (realized in find_class_by_code_object(self) nested function).

bubble() can be easily extended with *args, **kwargs.

import inspect
def bubble(*args, **kwargs):
    def find_class_by_code_object(back_self, method_name, code):
        for cls in inspect.getmro(type(back_self)):
            if method_name in cls.__dict__:
                method_fun = getattr(cls, method_name)
                if method_fun.im_func.func_code is code:
                    return cls

    frame = inspect.currentframe().f_back
    back_self = frame.f_locals['self']
    method_name = frame.f_code.co_name

    for _ in xrange(5):
        code = frame.f_code
        cls = find_class_by_code_object(back_self, method_name, code)
        if cls:
            super_ = super(cls, back_self)
            return getattr(super_, method_name)(*args, **kwargs)
        try:
            frame = frame.f_back
        except:
            return



class A(object):
    def test(self):
        print "A.test()"

class B(A):
    def test(self):
        # instead of "super(B, self).test()" we can do
        bubble()

class C(B):
    pass

c = C()
c.test() # works!

b = B()
b.test() # works!

If anyone has a better idea, let's hear it.

Known bug: (thanks doublep) If C.test = B.test --> "infinite" recursion. Although that seems un-realistic for child class to actually have a method, that has been ='ed from parent's one.

Known bug2: (thanks doublep) Decorated methods won't work (probably unfixable, since decorator returns a closure)... Fixed decorator proble with for _ in xrange(5): ... frame = frame.f_back - will handle up to 5 decorators, increase if needed. I love Python!

Performance is 5 times worse than super() call, but we are talking about 200K calls vs a million calls per second, if this isn't in your tightest loops - no reason to worry.

Slava N
I'm not sure it will work for decorated functions. Also, I hope performance is not important at all in your usecase for `bubble()`.
doublep
Another (quite unlikely in real use) bug: if subclass `def foo...`, then does `bar = foo` then `bubble()` in `bar()` won't work as excepted because `method_name` will be incorrectly evaluated to "foo".
doublep
bar = foo: If I do `B.test_alias = B.test` and then `c.test_alias()`, it calls `A.test()` as expected. Performance seems to be OK, 5000 ns (nanoseconds) for bubble() vs 900ns for super(...). This is a drop, but I don't see it as a huge one.
Slava N
@Slava: Ah, right. For `super()` emulator that's exactly what's needed and is even the same what would happen if the method did direct call to parent's implementation. I guess it's time to sleep, I'm making too many logical errors ;)
doublep
@doublep: thanks for the help! :)
Slava N
A: 

Although this code should never be used for any normal purpose. For the sake of answering the question, here's something working ;)

import inspect

def test2():
    funcname = inspect.stack()[1][3]
    frame = inspect.currentframe().f_back
    self = frame.f_locals['self']

    return contains(self.__class__, funcname)

def contains(class_, funcname):
    if funcname in class_.__dict__:
        return class_

    for class_ in class_.__bases__:
        class_ = contains(class_, funcname)
        if class_:
            return class_
WoLpH
Well, that's basically what I did below, however don't you think that using inspect.getmro() is better than __bases__ ? Also you don't test that the function is actually the one that called it, class `C` might have `test` function too. My answer takes care of that.
Slava N
Indeed, I just couldn't find the mro method so I emulated it myself. Your method is definately better. Although I still argue that both are the wrong solution.
WoLpH