Is this a super bug?

Michele Simionato mis6 at pitt.edu
Mon Apr 21 16:32:37 CEST 2003


"Bjorn Pettersen" <BPettersen at NAREX.com> wrote in message news:<mailman.1050791852.9118.python-list at python.org>...
> M:\python>python
> Python 2.3a2 (#39, Feb 19 2003, 17:58:58) [MSC v.1200 32 bit (Intel)] on
> win32
> Type "help", "copyright", "credits" or "license" for more information.
> >>> class ff(str):
> ...    def foo(self):
> ...      print 'str.  getitem  ', str.  getitem  (self, 0)
> ...      print 'super(ff, self).  getitem  ', super(ff,
> self).  getitem  (0)
> ...      print 'super(ff, self)[0]', super(ff, self)[0]
> ...
> >>> f = ff('asdf')
> >>> f.foo()
> str.  getitem   a
> super(ff, self).  getitem   a
> super(ff, self)[0]
> Traceback (most recent call last):
>   File "<stdin>", line 1, in ?
>   File "<stdin>", line 5, in foo
> TypeError: unsubscriptable object
> 

This is not a bug, since super(cls,self) is supposed to return a super
object, and not self. Nevertheless super has various bugs: it cannot retrieve
properties, cannot retrieve __name__, does not work always properly
in multiple inheritance of a class and a metaclass, etc. If you google a bit,
you will find various posts of mine about problems with super.

At the end I got really tired, started studying attribute descriptors
(completely undocumented, AFAIK), spent a lot of time and come with the
following code.

Warning: this is quite non-trivial code and I don't claim I have tested it
in any possible situation.

def ancestor(C,S=None):
  """Returns the ancestors of the first argument with respect to the 
  MRO of the second argument. If the second argument is None, then 
  returns the MRO of the first argument."""
  if C is object:
      raise TypeError("There is no superclass of object")
  elif S is None or S is C:
      return list(C.__mro__)
  elif issubclass(S,C): # typical case
      mro=list(S.__mro__)
      return mro[mro.index(C):] # compute the ancestors from the MRO of S
  else:
      raise TypeError("S must be a subclass of C")

class convert2descriptor(object): # used in _super
  """To all practical means, this class acts as a function that, given an
  object, adds to it a __get__ method if it is not already there. The 
  added __get__ method is trivial and simply returns the original object, 
  independently from obj and cls."""
  def __new__(cls,a):
      if hasattr(a,"__get__"): # do nothing
          return a # a is already a descriptor
      else: # creates a trivial attribute descriptor
          cls.a=a
          return object.__new__(cls,a)
  def __get__(self,obj,cls=None):
      "Returns self.a independently from obj and cls"
      return self.a

def _super(C,S,methname):
  """Internal function invoking ancestor. Returns an attribute descriptor 
  object."""
  if methname=='__name__': # special case
      meth=ancestor(C,S)[1].__name__
  else:
      for c in ancestor(C,S)[1:]:
          meth=c.__dict__.get(methname)
          if meth: break # if found
  if not meth: raise AttributeError,methname # not found 
  return convert2descriptor(meth) # if needed

class Super(object):
  """Invoked as Super(cls,obj).meth returns the supermethod of
  cls with respect to the MRO of obj. If obj is a subclass of cls,
  it returns the unbound supermethod of obj; otherwise, if obj is an
  instance of some subclass of cls, it returns the obj-bound method."""
  def __init__(self,cls,obj):
      self.cls=cls
      self.obj=obj
  def __getattribute__(self,name): # how to redefine __getattribute__ properly
      obj=object.__getattribute__(self,'obj')
      cls=object.__getattribute__(self,'cls')
      if hasattr(obj,'__bases__') and issubclass(obj,cls):
          # if obj is a subclass, return unbound method
          return _super(cls,obj,name).__get__(None,obj)
      else: # if obj is an instance, return bound method
          S=type(obj)
          return _super(cls,S,name).__get__(obj,S)

# few checks

class ExampleBaseClass(object):
  """Contains a regular method 'm', a staticmethod 's', a classmethod 
  'c', a property 'p' and a data attribute 'd'."""
  m=lambda self: 'regular method'
  s=staticmethod(lambda : 'staticmethod')
  c=classmethod(lambda cls: 'classmethod')
  p=property(lambda self: 'property')
  d='data'

class C(ExampleBaseClass): pass
c=C()
print Super(C,C).p #<property object>
print Super(C,c).p #property
print Super(C,c).__name__ # ExampleBaseClasss
print Super(C,C).__name__ # ExampleBaseClass




More information about the Python-list mailing list