Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

super() in Python 2.x without args

Tags:

python

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?

like image 838
Slava V Avatar asked Apr 24 '10 23:04

Slava V


1 Answers

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.

like image 186
Slava V Avatar answered Oct 08 '22 10:10

Slava V