[Python-Dev] Property inheritance in Python

Torsten Landschoff torsten at debian.org
Sun Apr 25 17:02:55 CEST 2010


Hi Python experts.

[It should be obvious, but you can run the code in this message via
python -m doctest body.txt if you saved it as body.txt]

In an application I develop on I want to use properties instead of the
getter/setter paradigm. I ran into a problem overriding a property in
a subclass. While I know how to do it with current Python, I propose
changing the behaviour to be more consistent.

For presentation, here is a stupid example using getters and setters:

   >>> class BaseGetterSetter(object):
   ...     def get_p(self):
   ...         return self._p
   ...     def set_p(self, value):
   ...         self._p = value
   >>> class DerivedGetterSetter(BaseGetterSetter):
   ...     def get_p(self):
   ...         return super(DerivedGetterSetter, self).get_p() * 2
   ...     def set_p(self, value):
   ...         super(DerivedGetterSetter, self).set_p(value / 2)
   >>> d = DerivedGetterSetter()
   >>> d.set_p(42)
   >>> d._p
   21
   >>> d.get_p()
   42

When translating this to use properties, I would come up with
something like this:

   >>> class BaseProp(object):
   ...     @property
   ...     def p(self):
   ...         return self._p
   ...     @p.setter
   ...     def p(self, value):
   ...         self._p = value
   >>> class DerivedProp(BaseProp):
   ...     @property
   ...     def p(self):
   ...         return super(DerivedProp, self).p * 2
   ...     @p.setter
   ...     def p(self, value):
   ...         super(DerivedProp, self).p = value / 2
   >>> d = DerivedProp()
   >>> d._p = 21
   >>> d.p
   42
   >>> d.p = 50
   Traceback (most recent call last):
      ...
   AttributeError: 'super' object has no attribute 'p'

As can be seen, using super like in the getter/setter approach above
works for fget but not for fset. Support for using the getter via
super() was added for Python 2.3 according to
http://mail.python.org/pipermail/python-dev/2003-April/034702.html


I think it would be more consistent to be able to access the __set__
call via super() as well.


Working around
--------------

The problematic code boils down to

   >>> super(DerivedProp, d).p = 1
   Traceback (most recent call last):
      ...
   AttributeError: 'super' object has no attribute 'p'

which can be worked around like this:

   >>> BaseProp.p.fset(d, 25)
   >>> BaseProp.p.__set__(d, 10)

I'd rather use the method resolution order computed by Python so that
mixin classes can extend the behaviour of properties. For that, one
can extend super:

   >>> class duper(super):
   ...     def __setattr__(self, name, value):
   ...         mro = self.__self_class__.__mro__
   ...         for pos in xrange(len(mro)):
   ...             if mro[pos] == self.__thisclass__:
   ...                 break
   ...         for pos in xrange(pos+1, len(mro)):
   ...             tmp = mro[pos]
   ...             if isinstance(tmp, type) and name in tmp.__dict__:
   ...                 desc = tmp.__dict__[name]
   ...                 desc.__set__(self.__self__, value)
   ...                 return
   >>> duper(DerivedProp, d).p = 100
   >>> d.p
   200


Extending super
---------------

I wrote a test case for the super() behaviour as I would like it to be
and implemented the __setattr__ of duper above into super's C
implementation. The code is not of production quality and there are
some open questions. But I figured that I'd rather ask for opinions
before spending even more time on this.

The code is available on launchpad at this URL:
https://code.launchpad.net/~torsten/python/descr-set-via-super


What do you think, do you agree with my understanding or am I
completely wrong?

Hoping for some insightful comments,

Torsten



More information about the Python-Dev mailing list