Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #11004 > unrolled thread

Restricted attribute writing

Started byJohn O'Hagan <research@johnohagan.com>
First post2011-08-08 01:35 +1000
Last post2011-08-08 14:59 +1000
Articles 8 — 5 participants

Back to article view | Back to comp.lang.python


Contents

  Restricted attribute writing John O'Hagan <research@johnohagan.com> - 2011-08-08 01:35 +1000
    Re: Restricted attribute writing Roy Smith <roy@panix.com> - 2011-08-07 12:07 -0400
      Re: Restricted attribute writing Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-08-08 02:31 +1000
        Re: Restricted attribute writing Rafael Durán Castañeda <rafadurancastaneda@gmail.com> - 2011-08-07 18:53 +0200
        Re: Restricted attribute writing Rafael Durán Castañeda <rafadurancastaneda@gmail.com> - 2011-08-07 18:56 +0200
    Re: Restricted attribute writing Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-08-08 03:07 +1000
      Re: Restricted attribute writing Chris Angelico <rosuav@gmail.com> - 2011-08-07 19:21 +0100
      Re: Restricted attribute writing John O'Hagan <research@johnohagan.com> - 2011-08-08 14:59 +1000

#11004 — Restricted attribute writing

FromJohn O'Hagan <research@johnohagan.com>
Date2011-08-08 01:35 +1000
SubjectRestricted attribute writing
Message-ID<mailman.2010.1312731312.1164.python-list@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

[toc] | [next] | [standalone]


#11005

FromRoy Smith <roy@panix.com>
Date2011-08-07 12:07 -0400
Message-ID<roy-0C3A61.12074507082011@news.panix.com>
In reply to#11004
In article <mailman.2010.1312731312.1164.python-list@python.org>,
 John O'Hagan <research@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.

[toc] | [prev] | [next] | [standalone]


#11006

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2011-08-08 02:31 +1000
Message-ID<4e3ebdc8$0$29991$c3e8da3$5496439d@news.astraweb.com>
In reply to#11005
Roy Smith wrote:

> In article <mailman.2010.1312731312.1164.python-list@python.org>,
>  John O'Hagan <research@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

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

[toc] | [prev] | [next] | [standalone]


#11007

FromRafael Durán Castañeda <rafadurancastaneda@gmail.com>
Date2011-08-07 18:53 +0200
Message-ID<mailman.2011.1312735996.1164.python-list@python.org>
In reply to#11006

[Multipart message — attachments visible in raw view] — view raw

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.lang.python@pearwood.info>

> Roy Smith wrote:
>
> > In article <mailman.2010.1312731312.1164.python-list@python.org>,
> >  John O'Hagan <research@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
>
> 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
>
> --
> http://mail.python.org/mailman/listinfo/python-list
>

[toc] | [prev] | [next] | [standalone]


#11008

FromRafael Durán Castañeda <rafadurancastaneda@gmail.com>
Date2011-08-07 18:56 +0200
Message-ID<mailman.2012.1312736167.1164.python-list@python.org>
In reply to#11006

[Multipart message — attachments visible in raw view] — view raw

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 <
rafadurancastaneda@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.lang.python@pearwood.info>
>
>> Roy Smith wrote:
>>
>> > In article <mailman.2010.1312731312.1164.python-list@python.org>,
>> >  John O'Hagan <research@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
>>
>> 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
>>
>> --
>> http://mail.python.org/mailman/listinfo/python-list
>>
>
>

[toc] | [prev] | [next] | [standalone]


#11009

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2011-08-08 03:07 +1000
Message-ID<4e3ec653$0$29987$c3e8da3$5496439d@news.astraweb.com>
In reply to#11004
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

[toc] | [prev] | [next] | [standalone]


#11012

FromChris Angelico <rosuav@gmail.com>
Date2011-08-07 19:21 +0100
Message-ID<mailman.2015.1312741278.1164.python-list@python.org>
In reply to#11009
On Sun, Aug 7, 2011 at 6:07 PM, Steven D'Aprano
<steve+comp.lang.python@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

[toc] | [prev] | [next] | [standalone]


#11024

FromJohn O'Hagan <research@johnohagan.com>
Date2011-08-08 14:59 +1000
Message-ID<mailman.2023.1312779580.1164.python-list@python.org>
In reply to#11009
On Mon, 08 Aug 2011 03:07:30 +1000
Steven D'Aprano <steve+comp.lang.python@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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web