Setting and using @property

Good afternoon, I am starting to study about object orientation and I am not able to understand very well about what function and when to use @property. But from what I understood until the moment it is a way to replace the 'get' and 'set', but I could not understand what makes it better than 'get' and 'set'.

Author: Pedro Henrique, 2019-02-24

1 answers

A bit of context before: traditionally, academic texts on object orientation speak that attributes and methods have to be separated into "public" and "private" (and, some languages put a few more distinctions there in between).

The Java language in particular ends up suggesting-through syntax and practices - that most of the attributes be private, and that for each one that is interesting to be manipulated by users of the class, you make a couple of functions - one to get the value of the attribute, another to write the value of the attribute-the "getter" and "setter". Due to the popularity of Java as an "Object-Oriented Language Model", these practices have been confused in much literature with what O. O. asks for.

In Python, on the other hand, the concept of "public and private" does not exist in the syntax of the language. There is, yes, a style convention that says that attribute names, methods, and functions started with _ (a single underscore) should not be used by users of a class, only by the implementers themselves - and that the functioning of these methods and functions may change without any notice.

So for most attributes in Python, the most common is to leave them simply as an instance attribute, which any user of the class can read or change without relying on any other mechanism, as in:

class Ponto:
   def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

However, the "getters " and" setters " can have additional features besides simply recording the Requested value - can transform the stored value, or check if the value is ok.

In Python, there is a very powerful mechanism, called the Descriptor Protocol, which causes a class attribute that is an object that has methods with some special names to be used differently in the instances of that class (and even in the class).

property is a function built-in Python that returns an object with these properties. Even it can be used as a function, it does not need to be used with the decorator syntax ("decorator" - used with @ in the line preceding the function declaration).

Basically property allows you to declare a function to get the value of an attribute, and optionally functions to function as the 'setter' and 'deleter' of that attribute.

For example, in the class"."above, if I want always round the value of " x " and " y " in the reading, and allow only numeric values to be entered, it can be described like this:

from numbers import Number

class Ponto:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    def _get_x(self):
        return round(self._x)

    def _set_x(self, value):
        if not isinstance(value, Number):
            raise TypeError("...")
        self._x = value

    x = property(_get_x, _set_x)

    def _get_y(self):
        return round(self._x)

    def _set_y(self, value):
        if not isinstance(value, Number):
            raise TyperErro("...")
        self._y = value

    y = property(_get_y, _set_y)

Notice that I used property as a normal function - I could at the end of the class even delete the methods from the body of the function, they will be used only indirectly by the objects returned by property - just add the line:

del _get_x, _set_x, _get_y, _set_y 

Inside the class.

The class attributes " x "and" y " now contain "descriptors": special objects with methods __get__ and __set__, which will be called automatically by the language, whenever someone makes an assignment or tries to read any of these attributes. These methods will call the original functions _get_x, (and etc... ) which were declared there.

The property call was created to be used as above when objects of Type " new " were created in Python 2.2 . In subsequent versions, with the addition of decorators syntax (Python 2.3), the property has been modified to be used as a decorator (Python 2.6). In this case, the first decorated function is always the "getter" of the attribute, and its name is used as the name of the special attribute - and the object returned after this "decoration" happens has the setter and deleter attributes that can be used to decorate the functions to set the values (and delete, if applicable).

The above Class looks like this with the current syntax:

from numbers import Number

class Ponto:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

    @property
    def x(self):
        return round(self._x)

    @x.setter
    def x(self, value):
        if not isinstance(value, Number):
            raise TypeError("...")
        self._x = value

    # idem para "y"

Em summary

Unless you need the functionality to transform/verify an attribute when it is assigned or read, you do not need to use the properties. On the other hand, a normal instance attribute can be promoted at any time to a property when your code evolves, and this will not introduce any mismatches - all code that uses your class as a normal attribute will continue to work (as long as it is putting your class into a normal attribute). valid values in the attribute) - since the syntax that calls setter remains the assignment with "=".

As a last example, see working in interactive mode. (I pasted that same class into the IPython prompt, but added two print into the Getter and setter of the x):

In [243]: from numbers import Number
     ...: 
     ...: class Ponto:
     ...:     def __init__(self, x=0, y=0):
     ...:         self.x = x
     ...:         self.y = y
     ...:     @property
     ...:     def x(self):
     ...:         print("lendo x")
     ...:         return round(self._x)
     ...:     
     ...:     @x.setter
     ...:     def x(self, value):
     ...:         print("escrevendo x")
     ...:         if not isinstance(value, Number):
     ...:             raise TyperError("...")
     ...:         self._x = value
                  ...

In [244]: a = Ponto()
escrevendo x

In [245]: a.x = 1.2
escrevendo x

In [246]: a.x
lendo x
Out[246]: 1

In [247]: a.x = "Um ponto dois"
escrevendo x
---------------------------------------------------------------------------
Type Error                                 Traceback (most recent call last)
<ipython-input-247-0b026b739095> in <module>()
----> 1 a.x = "Um ponto dois"

<ipython-input-243-0bc80d41c9e1> in x(self, value)
     15         print("escrevendo x")
     16         if not isinstance(value, Number):
---> 17             raise TypeError("...")
     18         self._x = value
     19 

TypeError: ...
 12
Author: jsbueno, 2019-11-21 20:21:44