[Python-ideas] superarg: an alternative to super()

Anthony Tolle artomegus at gmail.com
Wed Apr 9 03:55:17 CEST 2008


I was testing the new functionality of super() in Python 3.0a4, and
noted the current "limitations":

------------------------------------------------------------
Test 1 - define method outside class definition

>>> class A:
...  def f(self):
...   return 'A'
...
>>> def B_f(self):
...  return super().f() + 'B'
...
>>> class B(A):
...  f = B_f
...
>>> B().f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in B_f
SystemError: super(): __class__ cell not found

------------------------------------------------------------
Test 2 - Call super() from inner function

>>> class A:
...  def f(self):
...   return 'A'
...
>>> class B(A):
...  def f(self):
...   def inner():
...    return super().f() + 'B'
...   return inner()
...
>>> B().f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in f
  File "<stdin>", line 4, in inner
SystemError: super(): no arguments

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

Not satisfied, I started work on another way to simplify using
super().  I call it superarg, and it doesn't have any of the above
limitations.  It only takes twenty-six lines to implement in Python,
and doesn't rely on bytecode hacks or stack frame inspection.

What superarg does do is provide a decorator that inserts the super
object into the method's argument list, before the self argument.  In
the following Python-only implementation, correct behavior does depend
on using a special metaclass.

Here it is, including a test suite (tested in Python 3.0a4):

------------------------------------------------------------
# superarg.py

from types import MethodType

class superarg_method():
    def __init__(self, callable, cls=None):
        self.callable = callable
        self.cls = cls
    def __get__(self, obj, objtype=None):
        return self if obj is None else MethodType(self, obj)
    def __call__(self, obj, *args, **kwargs):
        return self.callable(super(self.cls, obj), obj, *args, **kwargs)
    def withclass(self, cls):
        return self.__class__(self.callable, cls)

class superarg_classmethod(superarg_method):
    def __get__(self, obj, objtype=None):
        return MethodType(self, type(obj) if objtype is None else objtype)

class superarg_meta(type):
    def __init__(cls, name, bases, dict):
        for name, value in dict.items():
            if isinstance(value, superarg_method):
                type.__setattr__(cls, name, value.withclass(cls))
    def __setattr__(cls, name, value):
        if isinstance(value, superarg_method):
            value = value.withclass(cls)
        type.__setattr__(cls, name, value)

# implementation ends here - test suite follows:

if __name__ == '__main__':
    class A(metaclass=superarg_meta):
        def f(self):
            return 'A(%s)' % (self.name,)
        @classmethod
        def cm(cls):
            return 'A(%s)' % (cls.__name__,)

    # Standard use
    class B(A):
        @superarg_method
        def f(super, self):
            return 'B' + super.f()
        @superarg_classmethod
        def cm(super, cls):
            return 'B' + super.cm()

    # Reference super in inner function
    class C(A):
        @superarg_method
        def f(super, self):
            def inner():
                return 'C' + super.f()
            return inner()
        @superarg_classmethod
        def cm(super, cls):
            def inner():
                return 'C' + super.cm()
            return inner()

    # Define functions before class definition
    @superarg_method
    def D_f(super, self):
        return 'D' + super.f()

    @superarg_classmethod
    def D_cm(super, cls):
        return 'D' + super.cm()

    class D(B, C):
        def __init__(self, name):
            self.name = name
        f = D_f
        cm = D_cm

    # Define functions after class definition
    class E(C, B):
        def __init__(self, name):
            self.name = name

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

    @superarg_classmethod
    def E_cm(super, cls):
        return 'E' + super.cm()

    E.f = E_f
    E.cm = E_cm

    # Test D
    d = D('d')
    assert d.f()  == 'DBCA(d)' # Normal method, instance binding
    assert D.f(d) == 'DBCA(d)' # Normal method, class binding
    assert d.cm() == 'DBCA(D)' # Class method, instance binding
    assert D.cm() == 'DBCA(D)' # Class method, class binding

    # Test E
    e = E('e')
    assert e.f()  == 'ECBA(e)'
    assert E.f(e) == 'ECBA(e)'
    assert e.cm() == 'ECBA(E)'
    assert E.cm() == 'ECBA(E)'

    # Test using D's methods in E
    E.cm = D_cm
    E.f = D_f
    assert e.f()  == 'DCBA(e)'
    assert E.f(e) == 'DCBA(e)'
    assert e.cm() == 'DCBA(E)'
    assert E.cm() == 'DCBA(E)'

    # Why not use E.cm = D.cm?  Because D.cm returns a bound method
    # (this would also be the case without using superarg).  Instead,
    # one would need to use E.cm = D.__dict__['cm']

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

The decorators only need to be applied where necessary.  If a method
doesn't need the super object, then it doesn't need the decorator.

I also tested implementing the decorators in C (as built-in types),
which works even better because I was able to put the class fix-ups in
type_new and type_setattro.  Thus, the special metaclass is no longer
needed.

The only times it wouldn't work right is if the end-user implemented a
metaclass that never calls type's __new__ or __setattr__ methods.
Avoiding type.__new__ should be fairly rare, but I'm not sure about
__setattr__.  I could probably extend the types so users can do their
own fix-ups if need be.  However, if a user is customizing a class
that much, then I wonder if they would make use of superarg anyway.

Whether or not there is any perceived value in adding this to a future
version of Python, perhaps someone will find it useful enough to add
to one of their own projects (and back-porting it to Python 2.x should
be fairly simple).

--
Anthony Tolle



More information about the Python-ideas mailing list