| 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.valThe 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.valThe 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.