Revision History | |
---|---|
Revision 0.1 | 2005-11-02 |
Initial draft. |
Abstract
This article describes an alternative to Python's built-in
property
function which allows the user to
override, in derived classes, the methods used by this function.
Table of Contents
This article describes an alternative to Python's built-in
property
function which allows the user to override,
in derived classes, the methods used by this function. The
property
function is one of
Python's built
in functions; it returns an object which implements Python's descriptor
protocol in a well-defined way. Raymond Hettinger provides a clear
exposition of descriptors in his article
How-To
Guide for Descriptors
[Het04], which
includes a brief discussion of the property
function
in the section
Properties
.
Hettinger provides a pure-Python implementation of the
property
function; an equivalent version can also be
found in the
comments of the built-in implementation (for Python 2.4.2). This
implementation illuminates exactly how this function works. As Hettinger
describes and the implementation verifies, the
property
function binds functions to a class
attribute that will be called when that attribute is retrieved, assigned,
or deleted; such attributes we will call
properties.
Example 1. A property in a derived class
class BePositive(object): def __init__(self): self._val = 0 def getval(self): return self._val def setval(self, value): self._val = abs(value) val = property(getval, setval) class TwoPositiveAttempt(BePositive): def setval(self, value): value = super(TwoPositive, self).setval(value) self._val = 2 * value b = BePositive() b.val = -8 print "First:", b.val t = TwoPositiveAttempt() t.val = -8 print "Second:", t.val
The above Python code adds the absolute value behavior to the assignment statement, resulting in the following output.
First: 8 Second: 8
What happens in a class which derives from one with such a property,
particularly one which overrides one or more of the methods used by its
properties? The properties will clearly be defined in the derived class;
Example 1, “A property in a derived class” shows—and the implementation
confirms—that such properties will always refer to the original method
from the base class. In this example, although we have overridden
setval
in the
TwoPositiveAttempt
derived class, when we assign
the val
property, the setval
from the BePositive
base class is called. This may
be what you want, but I want to be able to define properties in base
classes, and then to be able to simply override the methods assigned to
these properties in derived classes. To continue this example, I wanted
the second print statement to output Second:
16
.
If you often find yourself using the property
function to associate methods with class attributes, and you wish those
methods would be inherited (and overrideable) in subclasses, then this
exposition and implementation may be for you. Read on!
We can easily provide an alternative to the built-in version of
Python's property
function by basing our alternative
on the Python version of that built-in version. I call my version
OProperty
[1]; the implementation can be found in Figure 1, “
OProperty
class definition”.
Figure 1.
OProperty
class definition
class OProperty(object): """Based on the emulation of PyProperty_Type() in Objects/descrobject.c""" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError, "unreadable attribute" if self.fget.__name__ == '<lambda>' or not self.fget.__name__: return self.fget(obj) else: return getattr(obj, self.fget.__name__)() def __set__(self, obj, value): if self.fset is None: raise AttributeError, "can't set attribute" if self.fset.__name__ == '<lambda>' or not self.fset.__name__: self.fset(obj, value) else: getattr(obj, self.fset.__name__)(value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, "can't delete attribute" if self.fdel.__name__ == '<lambda>' or not self.fdel.__name__: self.fdel(obj) else: getattr(obj, self.fdel.__name__)()
Here, the OProperty
class adds to the
functionality of property
in a straightforward way.
It checks to see if the method that we've stored in this object has a name
and is not a lambda function. If the method is a lambda function or does
not have a name, then we fall back to the default behavior and just call
the bound method directly. Otherwise, we get the current method with the
name of the bound function on the current object and call that method. To
see this in action, see Example 2, “Overriding property methods with
OProperty
”.
Example 2. Overriding property methods with
OProperty
class BePositiveAgain(object): def __init__(self): self._val = 0 def getval(self): return self._val def setval(self, value): self._val = abs(value) val = OProperty(getval, setval) class TwoPositive(BePositiveAgain): def setval(self, value): super(TwoPositive, self).setval(value) self._val = 2 * self._val b = BePositiveAgain() b.val = -8 print "First:", b.val t = TwoPositive() t.val = -8 print "Second:", t.val
The BePostiveAgain
class in the above
Python code adds the absolute value behavior to the assignment
statement, resulting in the first line of the following output. The
TwoPositive
derived class then overrides the
functionality for the assignment statement (i.e. the
setval
method) to "cooperate"
with its superclass by first allowing the superclass definition of the
method to perform its processing, and then by performing its own
additional processing. Of course, for this to work, you need to have the
OProperty class from Figure 1, “
OProperty
class definition” defined.
First: 8 Second: 16
[Het04] How-To Guide for Descriptors . Python Software Foundation. 2004. http://users.rcn.com/python/download/Descriptor.htm.
[1] "O" for "Overrideable", "Object-oriented", "Other", or perhaps just for an exclamation of excitement. Take your pick, or come up with your own name.