Uma constante na orientação a objetos é a presença de propriedades dentro dos objetos quando precisamos armazenar dados para alguma manipulação posterior. É muito comum, principalmente quando viemos de outras linguagens, criarmos os  famosos getters and setters para manter também um bom nível de encapsulamento. Vamos ter como exemplo a classe Circulo abaixo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import math

class Circulo:
    def __init__(self, raio):
        self.raio = raio

    def area(self):
        return self.raio ** 2 * math.pi

    def circunferencia(self):
        return self.raio * 2 * math.pi


c = Circulo(1)
print(c.raio)
print(c.area())
print(c.circunferencia())

Perceba que para definirmos um círculo temos que passar o seu raio, com isso podemos calcular facilmente a sua área e circunferência. Precisamos também acessar ocasionalmente esse raio, podemos fazer isso através de dos getters que comentamos, mas isso não é o jeito do Python, ou como falaremos, o jeito mais Pythonico.

O Python oferece um jeito de lidar melhor com propriedades de objetos e o acesso a elas através do decorador @property. Este decorador irá cobrir uma função com o nome da propriedade que precisamos acessar e permitirá que acessemos valores do objeto como se fossem propriedades:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import math

class Circulo:
    def __init__(self, raio):
        self._raio = raio

    @property
    def raio(self):
        return self._raio

    def area(self):
        return self.raio ** 2 * math.pi

    def circunferencia(self):
        return self.raio * 2 * math.pi


c = Circulo(1)
print(c.raio)
print(c.area())
print(c.circunferencia())

Dessa vez armazenamos a propriedade com o underscore na frente, indicando que a propriedade é privada. Após isso criamos um método chamado raioque retornará este valor e o decoramos com @property. O segredo aqui é que a forma de acesso ao raio não sofreu alteração, não quebrando assim o código que já acessa o raio do circulo.

O mais legal é que esta funcionalidade não é apenas para propriedades armazenadas, como utilizamos o decorador em um método podemos utilizá-lo também em métodos que computam os valores, como o de área e circunferência:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import math

class Circulo:
    def __init__(self, raio):
        self._raio = raio

    @property
    def raio(self):
        return self._raio

    @property
    def area(self):
        return self.raio ** 2 * math.pi

    @property
    def circunferencia(self):
        return self.raio * 2 * math.pi


c = Circulo(1)
print(c.raio)
print(c.area)
print(c.circunferencia)

Agora acessamos os métodos que calculam algumas propriedades do circulo da mesma forma que acessamos as propriedades.

Este é um ponto interessante sobre como Python lida com a orientação a objetos e cobre uma lacuna que muitas vezes é tratada de maneira dogmática (no caso a criação de getters) de uma forma mais simples, inclusive permitindo o acesso direto a propriedades em um primeiro momento, da maneira que fizemos no nosso primeiro exemplo, para um encapsulamento maior posteriormente sem quebrar as interfaces do nosso código.

O outro lado dos getters, os famosos setters, iremos ver no próximo post junto com os deleters. Eles são muito fáceis e também demonstram a simplicidade criada para o programador devido uma interface de linguagem bem definida.