Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically update attributes of an object that depend on the state of other attributes of same object

Tags:

python

Say I have an class that looks like this:

class Test(object):
   def __init__(self, a, b):
      self.a = a
      self.b = b
      self.c = self.a + self.b 

I would like the value of self.c to change whenever the value of attributes self.a or self.b changes for the same instance.

e.g.

test1 = Test(2,4)
print test1.c # prints 6

test1.a = 3
print test1.c # prints = 6 

I know why it would still print 6, but is there a mechanism I could use to fire an update to self.c when self.a has changed. Or the only option I have is to have a method that returns me the value of self.c based on the current state of self.a and self.b

like image 508
name_masked Avatar asked Dec 19 '14 18:12

name_masked


2 Answers

Yes, there is! It's called properties.

Read Only Properties

class Test(object):
    def __init__(self,a,b):
        self.a = a
        self.b = b
    @property
    def c(self):
        return self.a + self.b

With the above code, c is now a read-only property of the Test class.

Mutable Properties

You can also give a property a setter, which would make it read/write and allow you to set its value directly. It would look like this:

class Test(object):
    def __init__(self, c = SomeDefaultValue):
        self._c = SomeDefaultValue
    @property
    def c(self):
        return self._c
    @c.setter
    def c(self,value):
        self._c = value

However, in this case, it would not make sense to have a setter for self.c, since its value depends on self.a and self.b.

What does @property mean?

The @property bit is an example of something called a decorator. A decorator actually wraps the function (or class) it decorates into another function (the decorator function). After a function has been decorated, when it is called it is actually the decorator that is called with the function (and its arguments) as an argument. Usually (but not always!) the decorated function does something interesting, and then calls the original (decorated) function like it would normally. For example:

def my_decorator(thedecoratedfunction):
    def wrapped(*allofthearguments):
        print("This function has been decorated!") #something interesting
        thedecoratedfunction(*allofthearguments)   #calls the function as normal
    return wrapped

@my_decorator
def myfunction(arg1, arg2):
    pass

This is equivalent to:

def myfunction(arg1, arg2):
    pass
myfunction = my_decorator(myfunction)

So this means in the class example above, instead of using the decorator you could also do this:

def c(self):
    return self.a + self.b
c = property(c)

They are exactly the same thing. The @property is just syntactic sugar to replace calls for myobject.c with the property getter and setter (deleters are also an option).

Wait - How does that work?

You might be wondering why simply doing this once:

myfunction = my_decorator(myfunction)

...results in such a drastic change! So that, from now on, when calling:

myfunction(arg1, arg2)

...you are actually calling my_decorator(myfunction), with arg1, arg2 sent to the interior wrapped function inside of my_decorator. And not only that, but all of this happens even though you didn't even mention my_decorator or wrapped in your function call at all!

All of this works by virtue of something called a closure. When the function is passed into the decorator in this way (e.g., property(c)), the function's name is re-bound to the wrapped version of the function instead of the original function, and the original function's arguments are always passed to wrapped instead of the original function. This is simply the way that closures work, and there's nothing magical about it. Here is some more information about closures.

Descriptors

So to summarize so far: @property is just a way of wrapping the class method inside of the property() function so the wrapped class method is called instead of the original, unwrapped class method. But what is the property function? What does it do?

The property function adds something called a descriptor to the class. Put simply, a descriptor is an object class that can have separate get, set, and delete methods. When you do this:

@property
def c(self):
    return self._c

...you are adding a descriptor to the Test class called c, and defining the get method (actually, __get__()) of the c descriptor as equal to the c(self) method.

When you do this:

@c.setter
def c(self,value):
    self._c

...you are defining the set method (actually, __set__()) of the c descriptor as equal to the c(self,value) method.

Summary

An amazing amount of stuff is accomplished by simply adding @property to your def c(self) method! In practice, you probably don't need to understand all of this right away to begin using it. However, I recommend keeping in mind that when you use @property, you are using decorators, closures, and descriptors, and if you are at all serious about learning Python it would be well worth your time to investigate each of these topics on their own.

like image 198
Rick supports Monica Avatar answered Oct 13 '22 13:10

Rick supports Monica


The simplest solution is to make c a read-only property:

class Test(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b

    @property
    def c(self):
        return self.a + self.b

Now every time you access test_instance.c, it calls the property getter and calculates the appropriate value from the other attributes. In use:

>>> t = Test(2, 4)
>>> t.c
6
>>> t.a = 3
>>> t.c
7

Note that this means that you cannot set c directly:

>>> t.c = 6

Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    t.c = 6
AttributeError: can't set attribute
like image 27
jonrsharpe Avatar answered Oct 13 '22 13:10

jonrsharpe