Improved super/autosuper

Delaney, Timothy C (Timothy) tdelaney at avaya.com
Mon Jul 5 03:50:34 CEST 2004


http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286195

This is a new version of super that automatically determines which
method needs to be called based on the existing stack frames. It's much
nicer for writing cooperative classes. It does have more overhead, but
at this stage I'm not so concerned about that - the important thing is
it actually works.

Note that this uses sys._getframe() magic ...

    import sys

    _builtin_super = super

    def super (self, *p, **kw):
        """
        Automatically determine the correct super method and call it.
        
        If there is no corresponding super method, there is no effect
(super just
        returns None). This assists in creating cooperative base
classes.

        This function is designed to be used with the autosuper
metaclass.

        Example of usage:

            __metaclass__ = autosuper

            class A:

                def __init__ (self, a, b):
                    print 'A.__init__'
                    print a, b
                    self.super(a, b)

            class B (A):

                def __init__ (self, a, b):
                    print 'B.__init__'
                    self.super(a, b)

            B(1, 2)

        produces:

            B.__init__
            A.__init__
            1 2
        """

        f = sys._getframe().f_back

        # Make sure that we're being called from a bound method
        instance = f.f_locals[f.f_code.co_varnames[0]]
        assert self is instance
        
        # We'll need this to look up the correct method in the base
classes
        fname = f.f_code.co_name

        # Find the method we're currently running by scanning the MRO
and comparing
        # the code objects - when we find a match, that's the class
whose method
        # we're currently executing.
        s = None
        si = None

        for c in type(self).__mro__:
            try:
                m = getattr(c, fname)
            except AttributeError:
                continue

            if m.im_func.func_code is f.f_code:
                s = c
                break

        # We should *never* fail to find the current class
        assert s is not None

        # Try to get a base class method. If we don't find one, we're
finished.
        try:
            m = getattr(_builtin_super(s, self), fname)
        except AttributeError:
            return None

        # If the code object for the super class is the same as the
current code
        # object, we've actually picked up the current class again -
which would
        # lead to infinite recursion. So we're finished.
        try:
            if m.func_code is f.f_code:
                return None
        except AttributeError:
            func_code = None

        if m is None:
            return None

        return m(*p, **kw)

    class autosuper (type):
        def __init__(cls, name, bases, dict):
            setattr(cls, 'super', super)

    if __name__ == '__main__':

        __metaclass__ = autosuper

        class A:
            def __init__ (self):
                print 'A.__init__'
                self.super()

            def test (self):
                print 'A.test'
                self.super()

        class B (A):
            def __init__ (self):
                print 'B.__init__'
                self.super()

            def test (self):
                print 'B.test'
                self.super()

        class C (A):
            def __init__ (self):
                print 'C.__init__'
                self.super()

            def test (self):
                print 'C.test'
                self.super()

        class D (B, C):
            def __init__ (self):
                print 'D.__init__'
                self.super()

            def test (self):
                print 'D.test'
                self.super()

        A().test()
        print
        B().test()
        print
        C().test()
        print
        D().test()

    ---------- Run ----------
    A.__init__
    A.test

    B.__init__
    A.__init__
    B.test
    A.test

    C.__init__
    A.__init__
    C.test
    A.test

    D.__init__
    B.__init__
    C.__init__
    A.__init__
    D.test
    B.test
    C.test
    A.test

    Output completed (0 sec consumed) - Normal Termination

Tim Delaney 




More information about the Python-list mailing list