As I write this, Python 2.2 is just out. It includes some new features to give classes "properties" like other languages (Visual Basic, Delphi) have them, or, more general, to have better control over object attributes.
I wrote a simple mechanism, called "shielded attributes", that has been around for a little while; yet I never bothered to publish it, because I assumed that Python 2.2 would make it obsolete. I now think that this may not be true. 2.2's properties seem to be a powerful, but relatively heavyweight mechanism, at least compared to the simplicty of shielded attributes. So I do think there is some merit to shielded even when using 2.2 and later: they provide a simple, lightweight alternative for those who want some control over the values some of a class's attributes get, but who don't care enough to define separate Property subclasses for all of them.
:: How it works ::
The premise of shielded is simple: there is a base class Shielded, and every class deriving from it can set certain restrictions on setting certain attributes, simply by defining a method that has the same name as the attribute in casu, preceded by an underscore. In other words, method _x restricts the values of attribute x.
Here's a simple example. Class Percentage has a perc attribute that must stay within 0 and 100, including the boundaries.
class Percentage(Shielded):
_perc = lambda s, x: 0 <= x <= 100
def __init__(self, perc=0):
self.perc = perc
Now, when we try to set the .perc attribute of an Percentage instance, it implictly calls _perc first, to check the value we're assigning. The rule is, that if we pass the value we're trying to set to the method, it must return true, otherwise an exception is raised.
>>> p = Percentage(50)
>>> p.perc
50
>>> p.perc = 192
Traceback (most recent call last):
File "<pyshell#16>", line 1, in ?
p.perc = 192
File "C:\python\test\general\shielded\shielded.py", line 12, in __setattr__
raise ValueError, "Cannot set attribute %s to %s" % (name,
ValueError: Cannot set attribute perc to 192
The code of the Shielded class is not heavy wizardry. It looks like this:
class Shielded:
def __setattr__(self, name, value):
if hasattr(self, "_"+name):
f = getattr(self, "_"+name)
assert callable(f), "Invalid restriction for %s: %s" % (name, f)
if f(value):
self.__dict__[name] = value
else:
raise ValueError, "Cannot set attribute %s to %s" % (name,
value)
else:
self.__dict__[name] = value
Some thoughts:
- I've used lambda here for brevity, but there's no reason why a "normal" method cannot be used.
- This code works in most recent Python versions. I'm not sure if pre-2.0 had callable, but that assertion is easily removed or replaced by a try-expect that tests if f can be called.
- You can make this class more strict by forbidding assignment to new attributes, IOW, creating new ones. I personally think it's more Pythonic to have the class be "laid back".
- Also, getting any attribute is still allowed. I cannot use a __getattr__ hook similar to this __setattr__, because it is only called for attributes that don't exist. Also, the previous argument may be true here too: it may be more Pythonic to keep the original behavior and let users inspect any attribute they want.
Note that this mechanism is less powerful than Python 2.2's properties, but it also is much less work to implement and use. When you have a Shielded base class that does what you want (being either strict or laid back), all you have to do to use it is add a "shield method" for every attribute you want to shield (often only one line per method), and make sure your class derives from Shielded.
--
For any questions, comments, suggestions, etc, you can write me.
|