On Mon, 08 Aug 2011 03:07:30 +1000 Steven D'Aprano <steve+comp.lang.pyt...@pearwood.info> wrote:
> 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. > Although experience shows you're usually right :) , I thought I had three problems, the third being what I perhaps wasn't clear enough about: that the two-integer containers live in a list which should only contain the two-integer things, but aside from that should be able to do all the other list operations on it. AFAIK making this attribute a property only protects it from incorrect assignment, but not from unwanted appends etc. That's what the other helper class Order is meant for, it subclasses list, and overrides __setitem__ to ensure every item is an OrderElement, and __getitem__ to ensure slices are the same class. I've also since realised it must override append, insert and extend. I think I need all this to ensure the required behaviour, including: s = SeqSim([[15, 2]], 12) s.order[0][1] = 100 s.order[0][1:] = [100] s.order += [[22, 11]] s.order *= 2 s.order[2] = [[15, 8]] s.order[1:5:2]) = [[1, 1],[2, 2]] s.order.extend([[1, 1],[2, 2]]) s.order.insert(2, [2, 29]) s.order.append([26, 24]) s.order.extend(s.order[1:3]) s.order = [[99, 99],[100, 100]] import random random.shuffle(s.order) etc [...] > 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 > [...] > 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? > Thanks for this, I can see that the __data attribute I was using was unnecessary and I've redone the OrderElement class accordingly, although I do want slicing and don't need dot-notation access: class OrderElement(): def __init__(self, length, a, b): self.__length=length self.__a = a self.__b = b self[:] = a, b def __setitem__(self, index, item): if isinstance(index, slice): for k, i in zip(range(*index.indices(2)), item): self[k] = i elif isinstance(item, int) and index in (0, 1): if index == 0: self.__a = item % self.__length elif index == 1: self.__b = item else: raise TypeError("OrderElement takes two integers") def __getitem__(self, index): if isinstance(index, slice): return [self[i] for i in range(*index.indices(2))] if index == 0: return self.__a if index == 1: return self.__b raise IndexError As for the rest, I take your point that a simple idea need not be simple to implement, and I'm starting to think my solution may be about as complicated as it needs to be. Regards, John -- http://mail.python.org/mailman/listinfo/python-list