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

Anthony Tolle artomegus at gmail.com
Wed Mar 5 08:49:02 CET 2008


On Tue, Mar 4, 2008 at 10:24 PM, Guido van Rossum <guido at python.org> wrote:
> Ehhh! The PEP's "reference implementation" is useless and probably
>  doesn't even work. The actual implementation is completely different.
>  If you want to help, a rewrite of the PEP to match reality would be
>  most welcome!

Yep, I knew the actual implementation was completely different from
the reference implementation.  I was really just trying to offer a
different take on 'fixing' super, even though I know it is too late to
suggest this type of change for python 3000.  That's one reason I
refrained from posting in the python-3000 list.

I was enamored with the idea of passing the super object as an actual
parameter to the method that needs it.  Using a decorator with
descriptor behavior (like staticmethod or classmethod) seemed the best
way to do this.

The only downside is that my implementation depends on using a
metaclass to fix up the decorator objects after the class definition
is completed (or catching assignment to class attributes after the
fact).

It would be nice if the decorator class could be self-contained
without depending on an associated metaclass.  However, the __get__
method of the decorator would have to dynamically determine the class
that the wrapped function belongs to.  Since functions can be defined
outside of a class and then arbitrarily assigned to a class attribute
(or even multiple classes!), this seems to be difficult.  In fact, the
code in my previous post has a bug related to this.

Which brings me to posting a new version of my code:
-- Defined __setattr__ in the metaclass to make demo code more
consistent (and less ugly).
-- Modified __init__ function in the metaclass so it doesn't generate
__get__ calls.
-- Fix-ups now create new instance of autosuper_method object instead
of modifying cls attribute of existing object.  Reason: assigning a
decorated function to multiple classes would modify the original
object, breaking functionality for all classes but one.
-- Known issue: cases such as E.f = D.f are not caught, because
__get__ on D.f doesn't return an instance of autosuper_method.  Can be
resolved by having autosuper_method.__get__ return a callable sublass
of autosuper_method.  However, it makes me wonder if my idea isn't so
hot after all. :/

Here's the new version:

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

#!/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 - prevents use
        # by methods of classes that don't subclass autosuper
        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):
        # fix up all autosuper_method instances in class
        for attr in clsdict:
            value = clsdict[attr]
            if isinstance(value, autosuper_method):
                setattr(cls, attr, autosuper_method(value.func, cls))

    def __setattr__(cls, attr, value):
        # catch assignment after class definition
        if isinstance(value, autosuper_method):
            value = autosuper_method(value.func, cls)
        type.__setattr__(cls, attr, value)

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

    @autosuper_method
    def E_f(super, self):
        return 'E' + super.f()

    E.f = E_f

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

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

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

Regardless of the flaws in my code, I still like the idea of a
decorator syntax to specify methods that want to receive a super
object as a parameter.  It could use the same 'magic' that allows the
new python 3000 super() to determine the method's class from the stack
frame, but doesn't depend on grabbing the first argument as the
instance (i.e. breaking use with inner functions).



More information about the Python-ideas mailing list