Property descriptors are a powerful tool for building easy to understand APIs. I use read-only properties liberally in my APIs, but it took a few months to arrive at an implementation idiom that I was happy with.
In Kangapy,1 each article is associated with a template, and the template may not change after the article object is constructed. It makes sense for the Article class to have a read-only 'template' attribute.
Based on what I had read of property descriptors, this was my first version of the descriptor code:
def __getTemplate(self):
return self.__template
template = property(__getTemplate, None, None,
"This article's defining Template")
While it provides a neat API for getting the article's template, the implementation is long winded and difficult to read. Compare it with the implementation of the equivalent getter method:
def getTemplate(self):
""" This article's defining Template
"""
return self.__template
Casting around for a better way to write property descriptors, I hit on a methodless idiom that I am still using:
template = property(lambda self: self.__template)
Simple derived value properties also work well:
templateName = property(lambda self: self.__template.name)
This methodless form of a property is simpler to write and read than the original form. On the down side, it doesn't have a doc string,2 and it still isn't as clear as a getter method.
1 Kangapy is my blogging software. It is currently undergoing its third complete rewrite. After this one, I'll release the source, I promise.
2 I found that burying the doc string inside the property function meant that I wasn't using it while I was editing the code. Since I'm not using pydoc, the doc string was useless.
Comments
Read only attributes? A bit old-school static-typeist for my liking. A bit bondage-and-discipline[1].
If you don't want to write to an attribute, just don't do it. If you do it by accident, well, your unit tests will pick it up, won't they. ;-)
[1] http://www.catb.org/~esr/jargon/html/B/bondage-and-discipline-language.html
Some attributes are read only because it makes sense, not because the class author is mean.
A simple enough example is a Rectangle class with r/w attributes "width" and "height", but a r/o attribute "area". The area isn't necessarily even computed in the getter - it might be more efficient to compute it when the width or height properties are set, and cached.
I would generally allow another programmer to modify the area property, but make it clear that it is an internal property (single underscore prefix). If there's a really good reason to make width*height not equal to area, then go ahead... but be warned that you're doing something odd.
> A bit old-school static-typeist for my liking.
I know. I know. I argued with myself for a week about it too. In the end, I decided that read-only attributes were good documentation - they remind me that I "shouldn't" be changing those values. The cost is minimal: a line of code up-front (which replaces the "DON'T MODIFY THIS VALUE" comment), and the need to change the code later if it turns out that I was wrong and the attribute should be mutable.
I've written some recipes related to automated property creation that you may find interesting:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/157768
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252180
Coincidentally, I had the same idea about a week ago and have been using the same lambda-based idiom for properties.
Here's a module I use for generating these properties:
http://www.webwareforpython.org/~ianb/w4py.org/Wiki/lib/propertymeta.py
It automatically looks for specially-named methods (ending with __get, __set, or __del) and turns those into properties. Then all you need is to use the naming convention and to set the metaclass.
For read-only variables you can probably make it simpler still, with some of the techniques that have already been described -- though a custom descriptor class might be best, and easier to write than you might expect.
I think a property is a concept, not a data member. When you access x.template, why should you _care_ if it's a stored or computed value? That's the real power of the property() function. Bertrand Meyer's Uniform Access Principle and all that. Your data can start as a field and morph to a function whenever.
The downside is the inconvenient syntax. Contrast that with Ruby's oh-so-elegant
for a read/write field, and set/get can be defined at any time in the lifecycle of the code:
class Foo def bar # ... end def bar=(x) # ... end end