[Python-ideas] new super redux (better late than never?)

Anthony Tolle artomegus at gmail.com
Wed Mar 5 03:36:36 CET 2008


I was looking at the reference implementation in PEP 3135 (New Super),
and I was inspired to put together a slightly different implementation
that doesn't fiddle with bytecode.  I know that the new super() in
python 3000 doesn't follow the reference implementation in the PEP,
but the code intrigued me enough to offer up this little tidbit, which
can be easily be used in python 2.5.

What I did was borrow the idea of using a metaclass to do a
post-definition fix-up on the methods, but added a new function
decorator called autosuper_method.  Like staticmethod or classmethod,
the decorator wraps the function using the non-data descriptor
protocol.

The method wrapped by the decorator will receive an extra implicit
argument (super) inserted before the instance argument (self).

One caveat about the decorator: it must be the first decorator in the
list (i.e. the outermost wrapper), or else the metaclass will not
recognize the wrapped function as an instance of the decorator class.

I think this implementation strikes me as more pythonic than the
spooky behavior of the new python 3000 super() built-in, and it is
more flexible because of the implicit argument design.  This allows
things like the ability to use the super argument in inner functions
without worrying about the 'first argument' assumption of python
3000's super().

The implementation follows, which is also called autosuper in
deference to the original reference implementation.  It includes a
demonstration of some of its flexibility:

------------------------------------------------------------

#!/usr/bin/env python
#
# autosuper.py

class autosuper_method(object):
    def __init__(self, func, cls=None):
        self.func = func
        self.cls = cls

    def __get__(self, obj, type=None):
        # return self if self.cls is not set yet
        if self.cls is None:
            return self

        if obj is None:
            # class binding - assume first argument is instance,
            # and insert superclass before it
            def newfunc(*args, **kwargs):
                if not len(args):
                    raise TypeError('instance argument missing')
                return self.func(super(self.cls, args[0]),
                                 *args,
                                 **kwargs)
        else:
            # instance binding - insert superclass as first
            # argument, and instance as second
            def newfunc(*args, **kwargs):
                return self.func(super(self.cls, obj),
                                 obj,
                                 *args,
                                 **kwargs)
        return newfunc

class autosuper_meta(type):
    def __init__(cls, name, bases, clsdict):
        # set cls attribute of all instances of autosuper_method
        for v in clsdict:
            o = getattr(cls, v)
            if isinstance(o, autosuper_method):
                o.cls = cls

class autosuper(object):
    __metaclass__ = autosuper_meta

if __name__ == '__main__':
    class A(autosuper):
        def f(self):
            return 'A'

    # Demo - standard use
    class B(A):
        @autosuper_method
        def f(super, self):
            return 'B' + super.f()

    # Demo - reference super in inner function
    class C(A):
        @autosuper_method
        def f(super, self):
            def inner():
                return 'C' + super.f()
            return inner()

    # Demo - define function before class definition
    @autosuper_method
    def D_f(super, self):
        return 'D' + super.f()

    class D(B, C):
        f = D_f

    # Demo - define function after class definition
    class E(B, C):
        pass

    # don't use @autosuper_method here!  The metaclass has already
    # processed E, so it won't be able to set the cls attribute
    def E_f(super, self):
        return 'E' + super.f()

    # instead, use the extended version of the decorator
    E.f = autosuper_method(E_f, E)

    d = D()
    assert d.f() == 'DBCA'      # Instance binding
    assert D.f(d) == 'DBCA'     # Class binding

    e = E()
    assert e.f() == 'EBCA'      # Instance binding
    assert E.f(e) == 'EBCA'     # Class binding

------------------------------------------------------------

P.S. I know that using the word 'super' as an argument name might be
frowned upon, but I'm just copying what I've seen done in the standard
python library (e.g. using 'list' as a local variable name :).
Anyway, it doesn't really hurt anything unless you wanted to call the
original super() built-in from the decorated method, which would kind
of defeat the purpose.

P.P.S. Something like this might have been offered up already.  I've
been searching the mail list archives for a while, and found a few
reference to using decorators, but didn't find any full
implementations.  This implementation also has the advantage of being
compatible with existing code.



More information about the Python-ideas mailing list