[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