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

Say I have a 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 change whenever the value of the attributes self.a or self.b changes for the same instance.

eg.

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

I know why it will print 6 anyway, but is there a mechanism I could use to start upgrading to self.c when self.a 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

+6
source share
2 answers

Yes there is! It was 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 now a read-only class of Test .

Mutable properties

You can also provide a setter property that will make it read / write and allow you to set its value directly. It will 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 be pointless 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 what is called a decorator. The decorator actually wraps a function (or class) that it decorates into another function (decorator function). After the function has been decorated when it is called, it is actually a decorator that is called with the function (and its arguments) as an argument. Usually (but not always!) A decorated function does something interesting, and then calls the original (decorated) function, as usual. For instance:

 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 that in the example above, instead of using a decorator, you can also do this:

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

This is exactly the same. @property is just syntactic sugar to replace myobject.c calls with getter and setter (also removed options).

Wait - how does it work?

You might be wondering why just do it once:

 myfunction = my_decorator(myfunction) 

... leads to such a drastic change! So now when calling:

 myfunction(arg1, arg2) 

... you actually call my_decorator(myfunction) , and arg1, arg2 sent to the wrapped inner function inside my_decorator . And not only that, but it all happens, even if you didn't even mention my_decorator or wrapped in your function call at all!

All this works by virtue of something called a closure. When a function is passed to the decorator in this way (for example, property(c) ), the name of the function is redefined into the wrapped version of the function instead of the original function, and the original arguments of the function are always passed to wrapped instead of the original function. This is just how closures work, and there is nothing magical about it. Here is more information on closing .

descriptors

So, to summarize so far: @property is just a way to wrap a class method inside the property() function, so the wrapped class method is called instead of the original method of the expanded class. But what is a property function? What is he doing?

The property function adds something called a handle to the class. Simply put, a descriptor is a class of objects that can have separate get, set, and delete methods. When you do this:

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

... you add a descriptor to the Test class called c and define the get method (actually, __get__() ) of the descriptor c equal to the c(self) method.

When you do this:

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

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

Summary

An amazing amount of things is achieved by simply adding @property to your def c(self) method! In practice, you probably do not need to immediately understand all this in order to start using it. However, I recommend keeping in mind that when using @property you use decorators, locks, and descriptors, and if you are serious about learning Python, you should take the time to learn each of these topics on their own.

+13
source

The simplest solution is to make c 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 getter property and computes the corresponding value from the other attributes. Using:

 >>> t = Test(2, 4) >>> tc 6 >>> ta = 3 >>> tc 7 

Please note that this means that you cannot directly install c :

 >>> tc = 6 Traceback (most recent call last): File "<pyshell#16>", line 1, in <module> tc = 6 AttributeError: can't set attribute 
+5
source

Source: https://habr.com/ru/post/979944/


All Articles