Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Restricted attribute writing

7 views
Skip to first unread message

John O'Hagan

unread,
Aug 7, 2011, 11:35:00 AM8/7/11
to pytho...@python.org
I'm looking for good ways to ensure that attributes are only writable such that they retain the characteristics the class requires.

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.

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. I'm interested in ideas for simpler solutions, and general advice on how to do this kind of thing in a straightforward way.


class OrderElement():
"""Container class which can only hold two integers
the first of which is a modulo of the 'length' arg"""
def __init__(self, lis, length):
self.__data = [None, None]
self.__length=length
self[:] = lis

def __setitem__(self, index, item):
if isinstance(index, slice):
inds = range(*index.indices(2))
for k, v in enumerate(item):
self[inds[k]] = v
elif isinstance(item, int):
if index == 0:
item %= self.__length
self.__data[index] = item
else:
raise TypeError("OrderElement takes two integers")

def __getitem__(self, index):
return self.__data[index]


class Order(list):
"""Can only contain OrderElements"""
def __init__(self, lis, length):
self.__length = length
self[:] = lis

def __setitem__(self, index, item):
if isinstance(index, slice):
item = [i if isinstance(i, OrderElement)
else OrderElement(i, self.__length)
for i in item]
elif not isinstance(item, OrderElement):
item = OrderElement(item, self.__length)
list.__setitem__(self, index, item)

def __getitem__(self, index):
"""Ensure slices are of the same class"""
if isinstance(index, slice):
return self.__class__(list.__getitem__(self, index),
self.__length)
return list.__getitem__(self, index)


class SeqSim():
"""Just the relevant bits of the main class"""
def __init__(self, lis, length):
self.__order = Order(lis, length)
self.length = length

@property
def order(self):
return self.__order

@order.setter
def order(self, lis):
if not isinstance(lis, Order):
lis = Order(lis, self.length)
self.__order = lis

--

John O'Hagan

Roy Smith

unread,
Aug 7, 2011, 12:07:46 PM8/7/11
to
In article <mailman.2010.1312731...@python.org>,

John O'Hagan <rese...@johnohagan.com> wrote:

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

Sounds like you're trying to do
http://en.wikipedia.org/wiki/Design_by_contract. Which is not a bad
thing. But, I think a more pythonic way to implement this would be to
verify behaviors, not types.

I would start by writing a assert_invarient() method which validates the
object. I'm guessing all you really need is that you can index [0] and
[1] and get ints, so test for that. Something like:

def assert_invarient(self):
try:
assert isinstance(data[0], int)
assert isinstance(data[1], int)
except:
raise ValueError

Then, call this from inside your __init__(), __setitem__(), etc.

Steven D'Aprano

unread,
Aug 7, 2011, 12:31:03 PM8/7/11
to
Roy Smith wrote:

Don't do that. assert is for testing program logic, not verifying data. The
problem with assert is that the user can turn all assertions off, simply by
launching Python with the -O switch. Your verification code then becomes:

def assert_invarient(self):
try:
pass
except:
raise ValueError

which is useless.

When should you use an assertion? If you've ever written code like this:

if condition:
do_something()
else:
# This should never happen. But you know what they say: code that
# can't happen, does!
raise RuntimeError('condition unexpectedly false')


that's a prime candidate for turning into an assertion:


assert condition, 'condition unexpectedly false'
do_something()

--
Steven

Rafael Durán Castañeda

unread,
Aug 7, 2011, 12:53:11 PM8/7/11
to pytho...@python.org
I think you might use a tuple instead of a list for OrderElement, that would make much easier your code:

class OrderElement(tuple):                                                                                                                                                                                                                                                    
    def __new__(cls, x, y):
        if not isinstance(x, int) or not isinstance(y, int):
            raise TypeError("Order element must receives two integers")                                                                                                                                   
        return tuple.__new__(cls, (x, y))


class Order(list):
    def __setitem__(self, item):
        assert isinstance(item, OrderElement)
        super(Order, self).__setitem__(item)


I didn't check your module condition since it isn't quite clear to me, but you could add a second condition two Order class.

2011/8/7 Steven D'Aprano <steve+comp....@pearwood.info>

Rafael Durán Castañeda

unread,
Aug 7, 2011, 12:56:04 PM8/7/11
to pytho...@python.org
The assert on Order should be an if ... raise, like OrderElement, sorry for the mistake and repost

El 7 de agosto de 2011 18:53, Rafael Durán Castañeda <rafaduran...@gmail.com> escribió:
I think you might use a tuple instead of a list for OrderElement, that would make much easier your code:

class OrderElement(tuple):                                                                                                                                                                                                                                                    
    def __new__(cls, x, y):
        if not isinstance(x, int) or not isinstance(y, int):
            raise TypeError("Order element must receives two integers")                                                                                                                                   
        return tuple.__new__(cls, (x, y))


class Order(list):
    def __setitem__(self, item):
        assert isinstance(item, OrderElement)
        super(Order, self).__setitem__(item)


I didn't check your module condition since it isn't quite clear to me, but you could add a second condition two Order class.
2011/8/7 Steven D'Aprano <steve+comp....@pearwood.info>
Roy Smith wrote:

Steven D'Aprano

unread,
Aug 7, 2011, 1:07:30 PM8/7/11
to
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

Chris Angelico

unread,
Aug 7, 2011, 2:21:15 PM8/7/11
to pytho...@python.org
On Sun, Aug 7, 2011 at 6:07 PM, Steven D'Aprano
<steve+comp....@pearwood.info> wrote:
> class ThingWithTwoIntegers(object):
>

I'm not a lisp expert, but this might well be called a cons cell. Or a "pair".

ChrisA

John O'Hagan

unread,
Aug 8, 2011, 12:59:23 AM8/8/11
to pytho...@python.org
On Mon, 08 Aug 2011 03:07:30 +1000

Steven D'Aprano <steve+comp....@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
[...]

[...]


> 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

0 new messages