[Python-Dev] Super and properties

Guido van Rossum guido@python.org
Wed, 16 Apr 2003 15:31:48 -0400


(I'm quoting the whole message below since this has been two weeks by
now.)

> From: =?iso-8859-1?Q?Gon=E7alo_Rodrigues?= <op73418@mail.telepac.pt>
> 
> Hi all,
> 
> Since this is my first post here, let me first introduce myself. I'm Gonšalo
> Rodrigues. I work in mathematics, mathematical physics to be more precise. I
> am a self-taught hobbyist programmer and fell in love with Python a year and
> half ago. And of interesting personal details this is about all so let me
> get down to business.
> 
> My problem has to do with super that does not seem to work well with
> properties. I posted to comp.lang.python a while ago and there I was advised
> to post here. So, suppose I override a property in a subclass,  e.g.
> 
> >>> class test(object):
> ...  def __init__(self, n):
> ...   self.__n = n
> ...  def __get_n(self):
> ...   return self.__n
> ...  def __set_n(self, n):
> ...   self.__n = n
> ...  n = property(__get_n, __set_n)
> ...
> >>> a = test(8)
> >>> a.n
> 8
> >>> class test2(test):
> ...  def __init__(self, n):
> ...   super(test2, self).__init__(n)
> ...  def __get_n(self):
> ...   return "Got ya!"
> ...  n = property(__get_n)
> ...
> >>> b = test2(8)
> >>> b.n
> 'Got ya!'
> 
> Now, since I'm overriding a property, it is only normal that I may want to
> call the property implementation in the super class. But the obvious way (to
> me at least) does not work:
> 
> >>> print super(test2, b).n
> Traceback (most recent call last):
>   File "<interactive input>", line 1, in ?
> AttributeError: 'super' object has no attribute 'n'
> 
> I know I can get at the property via the class, e.g. do
> 
> >>> test.n.__get__(b)
> 8
> >>>
> 
> Or, not hardcoding the test class,
> 
> >>> b.__class__.__mro__[1].n.__get__(b)
> 8
> 
> But this is ugly at best. To add to the puzzle, the following works, albeit
> not in the way I expected
> 
> >>> super(test2, b).__getattribute__('n')
> 'Got ya!'
> 
> Since I do not know if this is a bug in super or a feature request for it, I
> thought I'd better post here and leave it to your consideration.
> 
> With my best regards,
> G. Rodrigues

Hah!  I think I've resolved this, and I *still* don't know if it's a
bug report or a feature request. :-)

The crux of the matter is that super() has a specific exception for
data descriptors, of which properties are an example.  This means that
when looking for attribute 'x', if it finds a hit which is a data
descriptor, it ignores it and keeps looking.

It took me a while to understand why.  When I disabled the test,
exactly *one* unit test fails, and it wasn't immediately clear what
was going on.

It turns out that this test was asking for the __class__ attribute of
the super object itself, but it was getting the __class__ of the
instance.  Simplified:

  class C(object):
    pass
  print super(C, C()).__class__

This should print <type 'super'> and not <class '__main__.C'>, because
it would be really confusing if the super object, when inquired about
its class, masqueraded as another class.

How does skipping data descriptors accomplish this goal?  When super
does its search, the last class it looks at is 'object', at the end of
the MRO chain.  And this has a data descriptor for '__class__', which
describes the __class__ attribute of all objects.  If super were to
give this descriptor the usual treatment, it would call its __get__
method, and that would (in the above example) return the class C.

The CVS history mentions (for typeobject.c rev 2.120, shortly before
the final 2.2.0 release):

  super(C, C()).__class__ would return the __class__ attribute of C()
  rather than the __class__ attribute of the super object.  This is
  confusing.  To fix this, I decided to change the semantics of super
  so that it only applies to code attributes, not to data attributes.
  After all, overriding data attributes is not supported anyway.

Your message above makes a good case for overriding data attributes,
so I have to retract this.  But I don't want __class__ to return C, I
want it to return super.  So I'll change this back, and make an
explicit exception only for __class__.

And ok, I'm deciding now that this is a feature, which means that I'm
changing it in Python 2.3, but not backporting the change to Python
2.2.x.

Hope this helps!

--Guido van Rossum (home page: http://www.python.org/~guido/)