17

I have a class which has multiple attributes that are related, for example:

class SomeClass:
    def __init__(self, n=0):
        self.list = range(n)
        self.listsquare = [ x**2 for x in self.list ]

If I make an object normally that would no problem, with

a = SomeClass(10)

I will get 2 lists, a.list and a.listsquare.

Now if I want to make a empty object first, and assign one attribute to it, I want the other attributes to be automatically updated, for example if I do

b = SomeClass()
b.list = range(5,10)

I want b.listsquare to be automatically updated, and also the other way around (assign b.listsquare and auto update b.list). Is this possible? Is Class the right choice for this?


Thanks to you all, but I'm completely overwhelmed by all the different answers. Can anyone give a complete solution so I can learn write my own?

I would like to achieve a class Foo with 3 attributes length, list and listsquare such that:

  1. If I do a = Foo(3), I get a.length = 3, a.list = [0, 1, 2], a.listsquare = [0, 1, 4].
  2. If I do b = Foo().list = [5, 6], I get b.length = 2, b.listsquare = [25, 36].
  3. If I do c = Foo().listsquare = [4, 9], I get c.length = 2, c.list = [2, 3].
LWZ
  • 11,670
  • 22
  • 61
  • 79
  • 2
    The code you have given us doesn't work. classes are defined by 'class' not 'def' and are initialized via the 'def __init__(self, ...)' method. You have defined a function with 1 required param called 'self' and 1 optional param called 'n'. When you call SomeClass(10), the function tries to assign 'list' to the 10 object and fails. - oh, looks like the question is being reformatted... so never mind. – tdelaney Feb 16 '13 at 23:38
  • Sorry, I made the corrections. I'm not very familiar with it right now. – LWZ Feb 16 '13 at 23:42

4 Answers4

32

if updating one property due to an update on another property is what you're looking for (instead of recomputing the value of the downstream property on access) use property setters:

class SomeClass(object):
    def __init__(self, n):
        self.list = range(0, n)

    @property
    def list(self):
        return self._list
    @list.setter
    def list(self, val):
        self._list = val
        self._listsquare = [x**2 for x in self._list ]

    @property
    def listsquare(self):
        return self._listsquare
    @listsquare.setter
    def listsquare(self, val):
        self.list = [int(pow(x, 0.5)) for x in val]

>>> c = SomeClass(5)
>>> c.listsquare
[0, 1, 4, 9, 16]
>>> c.list
[0, 1, 2, 3, 4]
>>> c.list = range(0,6)
>>> c.list
[0, 1, 2, 3, 4, 5]
>>> c.listsquare
[0, 1, 4, 9, 16, 25]
>>> c.listsquare = [x**2 for x in range(0,10)]
>>> c.list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Ali-Akber Saifee
  • 4,406
  • 1
  • 16
  • 18
  • Does this mean I need to define 3 properties (`size`, `list`, `listsquare`) so when I update one of them the other 2 will update? – LWZ Feb 17 '13 at 00:44
  • that is correct. i've updated the above answer to work the way you wanted - the size attribute feel necessary anymore. – Ali-Akber Saifee Feb 17 '13 at 01:21
8

Absolutely. But use a property instead.

class SomeClass(object):
  def __init__(self, n=5):
    self.mylist = range(n)

  @property
  def listsquare(self):
    return [ x**2 for x in self.mylist ]

a = SomeClass()
a.mylist = [4, 5, 8]
print a.listsquare

Caching of the property value is left as an exercise for the reader.

Wingman4l7
  • 649
  • 1
  • 8
  • 23
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
5

Ignacio's @property solution is great but it recalculates the list every time you reference listsquare - that could get expensive. Mathew's solution is great, but now you have function calls. You can combine these with the 'property' function. Here I define a getter and a setter for my_list (I just couldn't call it 'list'!) that generates listsquare:

class SomeClass(object):

    def __init__(self, n=5):
        self.my_list = range(n)

    def get_my_list(self):
        return self._my_list

    def set_my_list(self, val):
        self._my_list = val
        # generate listsquare when my_list is updated
        self.my_listsquare = [x**2 for x in self._my_list]

    # now my_list can be used as a variable
    my_list = property(get_my_list, set_my_list, None, 'this list is squared')

x = SomeClass(3)
print x.my_list, x.my_listsquare
x.my_list = range(10)
print x.my_list, x.my_listsquare

This outputs:

[0, 1, 2] [0, 1, 4]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
BenMorel
  • 34,448
  • 50
  • 182
  • 322
tdelaney
  • 73,364
  • 6
  • 83
  • 116
4

You can also just use setter methods, like this:

class SomeClass:
    def __init__(self, n=5):
        self.set_list(range(n))

    def set_list(self, n):
        self.list = n
        self.listsquare = [ x**2 for x in self.list ]

b = SomeClass()
b.set_list(range(5,10))
Matthew Adams
  • 9,426
  • 3
  • 27
  • 43