Restricted attribute writing

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun Aug 7 19:07:30 CEST 2011


John O'Hagan wrote:

> I'm looking for good ways to ensure that attributes are only writable such
> that they retain the characteristics the class requires.

That's what properties are for.

> My particular case is a class attribute which is initialised as a list of
> lists of two integers, the first of which is a modulo remainder. I need to
> be able to write to it like a normal list, but want to ensure it is only
> possible to do so without changing that format.

Then you have two problems to solve.

First, you need a special type of list that only holds exactly two integers.
Your main class can't control what happens inside the list, so you need the
list to validate itself.

Secondly, you should use a property in your main class to ensure that the
attribute you want to be a special list-of-two-ints can't (easily) be
changed to something else.


> Below is a what I eventually came up with; a container class called
> OrderElement for the inner lists, and subclass of list called Order for
> the main attribute, which is a property of the main class, simplified
> below as SeqSim.  It works, but seems like a hell of a lot of code for a
> simple idea. 

And why should this be surprising? It might be a simple *idea*, but the
concrete execution of that idea is anything but simple. "Hey, let's fly to
Mars!" is a simple idea too.

Nevertheless, it does appear that your solution below is overly complicated.
Two helper classes just to have a thing that holds two ints... does it have
to be a list? Seems that if you're limited to exactly two items, a list is
pretty useless, since you can't insert, append, pop or delete items.

I'd take this approach instead:

# Untested.
class ThingWithTwoIntegers(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __getitem__(self, index):
        # Slicing not supported, because I'm lazy.
        if index < 0: index += 2
        if index == 0: return self.a
        elif index == 1: return self.b
        else: raise IndexError
    def __setitem__(self, index, value):
        # Slicing not supported, because I'm lazy.
        if index < 0: index += 2
        if index == 0: self.a = value
        elif index == 1: self.b = value
        else: raise IndexError
    def _geta(self):
        return self._a
    def _seta(self, value):
        if isinstance(value, (int, long)):  # drop long if using Python 3
            self._a = value
        else:
            raise TypeError('expected an int but got %s' % type(value))
    a = property(_geta, _seta)
    # and the same for b: _getb, _setb, making the obvious changes


There's a little bit of code duplication there, but it's 3am here and I'm
tired and besides, if I do all your work what would you do? *wink*

This gives you an object that holds two integers. You can access them either
by attribute name, "a" and "b", or by index, 0 and 1:

instance = ThingWithTwoIntegers(23, 42)
instance[0]
=> 23
instance.b
=> 42

Obviously this isn't a full blown list, but if you don't need all the
list-like behaviour (sorting, inserting, deleting items, etc.) why support
it?



-- 
Steven




More information about the Python-list mailing list