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

后端 未结 2 1361
闹比i
闹比i 2021-02-02 01:40

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 


        
相关标签:
2条回答
  • 2021-02-02 02:10

    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.

    0 讨论(0)
  • 2021-02-02 02:10

    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
    
    0 讨论(0)
提交回复
热议问题