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.
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.
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
Example 1 — A property in a derived class
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; “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 “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__)()
Figure 1 — OProperty class definition
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 “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 “OProperty class definition” defined.
First: 8 Second: 16
Example 2 — Overriding property methods with OProperty
Raymond Hettinger. How-To Guide for Descriptors. Python Software Foundation, 2004.
[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. |
This page was last modified on 2005-11-02 00:00:00Z.
This page was first published on .
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License.
See the version of this page with comments enabled to read or add comments.