super. could there be a simpler super?
Michele Simionato
michele.simionato at poste.it
Thu Jan 15 10:15:55 EST 2004
Kerim Borchaev <warkid at hotbox.ru> wrote in message news:<mailman.385.1074156644.12720.python-list at python.org>...
> Hello!
>
> Always when I use "super" I create a code duplication because class
> used as first arg to "super" is always the class where the method
> containing "super" was defined in:
> '''
> class C:
> def method(self):
> super(C, self).method()
> '''
>
> Obviously the methods like the one below doesn't work "right";-)
> '''
> def super(self):
> super(self.__class__, self)
> class C:
> def method(self):
> super(self).method()
> '''
>
> Is it possible that such a "super"(deducing class method declaration
> context) could appear in Python?
> (It seems to me that to implement a simple super something should be
> done during "compilation" of class declaration.)
>
> Best regards,
> Kerim mailto:warkid at hotbox.ru
``super`` is one of the trickiest Python constructs. In
http://www.python.org/2.2.3/descrintro.html Guido sketches
a metaclass solution which however is not quite satisfactory (for one,
it does not work with magic methods).
Some time ago I went to "fix" autosuper, but that required a major metaclass
hacking which was so deep that I have already forgotten what I did ;)
Nevertheless, I have still the files around, and my test suite runs okay
(this only means that the bugs are smarter than me) so I think I will post
the code. If somebody uses it and finds an unexpected behavior, please
send me a note. If somebody wants his head to explode, please try to
understand what safetype does ;)
Here are two examples of usage:
# example1.py: the diamond diagram
from super import autosuper
class A(object):
__metaclass__=autosuper
def m(self):
return "A"
class B(A):
def m(self):
return "B" + self.__super.m()
class C(A):
def m(self):
return "C" + self.__super.m()
class D(C, B):
def m(self):
return "D" + self.__super.m()
print D().m()
this prints DCBA.
#example2.py
from super import autosuper
class A(str):
__metaclass__=autosuper
def __new__(cls):
obj='A'+cls.__super.__new__(cls)
print obj
return obj
class B(A):
def __new__(cls):
obj="B" + cls.__super.__new__(cls)
print obj
return obj
class C(A):
def __new__(cls):
obj="C" + cls.__super.__new__(cls)
print obj
return obj
class D(C, B):
def __new__(cls):
obj="D" + cls.__super.__new__(cls)
print obj
return obj
D()
this prints
A
BA
CBA
DCBA
Here is the module super.py:
# super.py
from safetype import safetype # deep magic to avoid metaclass conflicts
class _super(object):
"""Helper descriptor, called by the ``Enable__super`` metaclass which will
take care of defining the ``__thisclass__`` attribute; it should not be
called directly, unless you really know what you are doing. Notice that
this ``_super`` is minimal, i.e. it does not define ``__new__``,
`` __init__`` or other special methods; this avoids the problems of the
standard ``super``."""
def __get__(self,obj,klass):
if obj is None: obj=klass
return super(self.__thisclass__,obj)
class autosuper(safetype):
"""Cooperative safe metaclass which defines a private attribute ``__super``
on its instances, containing a reference to the descriptor ``_super``.
This enable the cooperative syntax ``obj.__super.methodname`` as
sugar for ``super(callingclass,obj).methodname``."""
def __init__(cls,*args):
super(autosuper,cls).__init__(*args)
if len(args)==1 or args[0]=='superobject': return # do nothing
strippedname=args[0].lstrip('_')
# if the class name starts with underscores, they must
# be stripped; this is how the mangling mechanism works
sup=_super(); sup.__thisclass__=cls # trick to avoid __init__ in _super
setattr(cls,'_%s__super' % strippedname,sup)
Here is the module safetype.py:
# safetype.py
"""Deep, **DEEP** magic to remove metaclass conflicts.
``safetype`` provides the ``safetype`` metaclass, the mother of conflict-free
metaclasses. The suggested import syntax for usage in other modules is
from safetype import safetype as type
If you override ``__new__`` when you derive from ``safetype``,
you should do it cooperatively.
Example:
>>> from safetype import type
>>> class M(type):
... def __new__(mcl,*args):
... print 'creating a class from M'
... return super(M,mcl).__new__(mcl,*args)
>>> class N(type):
... def __new__(mcl,*args):
... print 'creating a class from N'
... return super(N,mcl).__new__(mcl,*args)
>>> class C:
... __metaclass__=M
creating a class from M
>>> class D:
... __metaclass__=N
creating a class from N
>>> class E(C,D):
... pass
creating a class from M
creating a class from N
>>> E.__class__ # automagically created
<class 'safetype.MN'>
>>> E.__metaclass__ # inherited from C
<class 'M'>
"""
import sys,sets,types,__builtin__
__type__=__builtin__.type
#the aboriginal 'type'; useful if you rebinds 'type' to 'safetype'
metadic={} # associates tuple of bases metaclasses to children metaclasses
class safetype(type):
"""Overrides the ``__new__`` method of the ``type`` metaclass, making the
generation of classes conflict-proof."""
# Seventeen lines of DENSE code!
def __new__(mcl,*args):
nargs=len(args)
if nargs==1: # works as __builtin__.type
return __type__(args[0])
elif nargs==3: # creates the class using the appropriate metaclass
n,b,d = args # name, bases and dictionary
mb=map(__type__,b) # metaclasses of the bases
meta=generatemetaclass([mcl,]+mb) # recursive
if mcl is meta: # meta is trivial, dispatch to the default __new__
return super(safetype,mcl).__new__(mcl,n,b,d)
elif is_less_specific(mcl,mb): # dispatch to meta.__new__
return meta.__new__(meta,n,b,d)
else: # non-trivial metaclass, dispatch to the right __new__
# (it will take a second round)
return super(mcl,meta).__new__(meta,n,b,d)
else:
raise TypeError('%s() takes 1 or 3 arguments' % mcl.__name__)
def generatemetaclass(metas):
"""Given a sequence of metaclasses, removes redundances and, if needed,
creates a new metaclass; returns the metaclass and updates the global
dictionary.of metaclasses. If the metaclass is already in the dictionary,
simply retrieves it."""
metabases=remove_redundant(metas)# metas have the priority
if metabases in metadic: # already generated metaclass
return metadic[metabases]
elif len(metabases)==1: # single metabase
meta=metabases[0]
else: # multiple metabases
metaname=''.join([m.__name__ for m in metabases])
meta=safetype(metaname,metabases,{})
return metadic.setdefault(metabases,meta)
def is_less_specific(c,ls):
"c is an ancestor of (at least) one class in the list ls."
for C in ls:
if issubclass(C,c) and C is not c: return True
return False
def remove_redundant(bases):
"""Returns a tuple of non-redundant base classes.
Given a sequence of base classes, a class is redundant if
1. it is duplicated;
2. it is implied by the others, i.e. it is an ancestor of at least one
of the other classes;
3. it is ClassType, the metaclass of old style classes.
For instance, if ``C`` is derived from ``B``, in the
sequence ``C,B`` the class ``B`` is redundant, since all its features are
already provided by ``C``. Therefore ``B``
is removed and ``remove_redundant`` returns the tuple ``(C,)``:
>>> class B(object): pass
...
>>> class C(B): pass
...
>>> import safetype; safetype.remove_redundant([C,B])
(<class 'C'>,)
"""
redundant=sets.Set((types.ClassType,)) # old style metaclass
ls=list(bases)
for c in bases:
if is_less_specific(c,ls) or c in redundant:
ls.remove(c)
else: # c is a redundant class to be removed if found
redundant.add(c)
return tuple(ls)
More information about the Python-list
mailing list