Ian Bicking: the old part of his blog

Easy Read-Only Attributes

(Looking for Descriptor Nit?)

I was thinking about PHP 5's new features, and some of the new Java- or C++-style restrictions you can place on classes (things like private and final). I'm no fan of these restrictions -- I expect programmers to take responsibility for their code, all I should do is advise, not enforce.

However, there is something useful there. It's nice to at least document what attributes you are supposed to be able to set (or not set), or what attributes you can expect to use on an object. So, with that in mind, here is a descriptor that creates a write-once attribute (attributes that are pure read-only are rather boring, after all!)

To understand how this works, read the How-To Guide for Descriptors.

import itertools

_setonce_count = itertools.count()

class setonce(object):

"""
Allows an attribute to be set once (typically in __init__), but
be read-only afterwards.

Example::

>>> class A(object):
... x = setonce()
>>> a = A()
>>> a.x
Traceback (most recent call last):
...
AttributeError: 'A' object has no attribute '_setonce_attr_0'
>>> a.x = 10
>>> a.x
10
>>> a.x = 20
Traceback (most recent call last):
...
AttributeError: Attribute already set
>>> del a.x
>>> a.x = 20
>>> a.x
20

You can also force a set to occur::

>>> A.x.set(a, 30)
>>> a.x
30
"""

def __init__(self, doc=None):
self._count = _setonce_count.next()
self._name = '_setonce_attr_%s' % self._count
self.__doc__ = doc

def __get__(self, obj, type=None):
if obj is None:
return self
return getattr(obj, self._name)

def __set__(self, obj, value):
try:
getattr(obj, self._name)
except AttributeError:
setattr(obj, self._name, value)
else:
raise AttributeError, "Attribute already set"

def set(self, obj, value):
setattr(obj, self._name, value)

def __delete__(self, obj):
delattr(obj, self._name)

if __name__ == '__main__':
import doctest
doctest.testmod()
This script can be found at svn://colorstudy.com/home/ianb/setonce.py
Created 15 Jul '04
Modified 25 Jan '05