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