Definition and use of @Property

Asked

Viewed 4,099 times

6

Good afternoon, I’m starting to study object orientation and I’m not getting a very good understanding of what the function is and when to use @Property. But from what I understood up to the moment it is a way to replace 'get' and 'set', but I could not understand what makes it better than 'get' and 'set'.

  • I will reply, but you will find more information here: https://answall.com/questions/186982/jeito-pythonico-de-definir-setters-e-getters/187#187187

  • Thanks for the help!!

1 answer

13


A little context before: traditionally, scholarly texts on object orientation talk about which attributes and methods have to be separated into "public" and "private" (and, some languages put a few more distinctions in between).

The Java language in particular ends up suggesting - through syntax and practices, that most attributes are private, and, for each one that is interesting to be manipulated by users of the class, you do a couple of functions - one to get the attribute value, another to write the value of the attribute - the "getter" and "Setter". By the popularity that Java had as an "Object-Oriented language model", these practices were, in many literature, confused with what O.O. asks for.

In Python, on the other hand, _doesn’t exist style which 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 operation of these methods and functions may change without any notice.

So, for most of the 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, "getters" and "setters" can have additional features besides simply recording the requested value - they can transform the stored value, or check if the value is ok.

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

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

Basically Property allows you to declare a function to get the value of an attribute, and optionally, functions to work as the 'Setter' and 'Deleter' of that attribute.

For example, in the class "." above, if I always want to round the value of "x" and "y" in the reading, and allow only numerical 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)

Note 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 objects returned by property- just add the line:

del _get_x, _set_x, _get_y, _set_y 

within the class.

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

The Property call was created to be used as above when "new" type objects were created in Python 2.2. In the following versions, with the addition of decorator syntax (Python 2.3), the Property was 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 attributes setter and deleter which 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"

in short

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 its code evolves, and this will not introduce any incompatibility - all code that uses its class as a normal attribute will continue to work (as long as it is putting valid values in the attribute) - since the syntax that calls the setter continues to be the assignment with "=".

As a last example, watch it work in interactive mode. (I pasted that same class at the Ipython prompt, but added two print on 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: ...

Browser other questions tagged

You are not signed in. Login or sign up in order to post.