Properties em Python ... como fazer encapsulamento pythônico

Na orientação a objetos no Python há alguns aspectos diferentes do tradicional apresentado em outras linguagens OO, mas nem por isso é algo para se espantar, vai de acordo com a filosofia de cada linguagem.

No Python todos os atributos de classe são públicos naturalmente, uma crítica que vejo muito é que não tem como restringir o acesso externo ao conteúdo das nossas classes, se valendo pelos modificadores de acesso : private, protected e public, e por isso não tem uma boa orientação a objetos( usando como parâmetro a que é ensinada no Java por exemplo , com tudo explícitamente exposto para o programador ali na tela.)

Mas como em linguagens de programação nem todas as features são feitas iguais, veremos a forma que o Python trata sua orientação a objetos no caso do encapsulamento.

Implementação Java-like do encapsulamento

Eu mesmo quando estava estudando Java e estava em transição para Python já usei essa forma de encapsulamento, ou seja, usava os famosos get_e set_ para acessar e definir os valores dos atributos da classe.

Então, com esse tipo implementado no Python usamos o que é mostrado tradicionamente por meio do get e set . .. vamos à um exemplo:

class Cupom:

    def __init__(self,nome,desconto):
        self.nome = nome.upper()
        self.desconto = desconto

    def getNome(self):
        return self.nome

    def setNome(self,nome):
        self.nome = nome.upper()

    # E por ai vai para os outros atributos

E na aplicação:

>>> cupom = Cupom("viva10",10)
>>> print(cupom.getNome())
VIVA10

Convenhamos que fazer isso sendo que não há nenhuma implementação diferente de retorno e nem de definição de valores é meio overhead de código, ainda mais no Python onde a atribuição e definição pode ser feita do modo:

class Cupom:

    def __init__(self,nome,codigo):
        self.nome = nome.upper()
        self.codigo = codigo

E na aplicação:

>>> cupom = Cupom("viva10",10)
>>> print(cupom.nome)
VIVA10

Mais limpo e intuitivo.

Afinal o que são properties

Isso foi para introduzir como é feito o acesso controlado à atributos da classe... agora vamos ver o conceito que o Python tem para fazer esse acesso controlado.

Bom, a "property" é um recurso que é responsável por realizar o acesso, a definição e a deleção em memória de atributos da classe. Ela é responsável por fazer todo o tratamento de acesso à dados internos de objetos... perfeito como alternativa para os gets e sets . E sua assinatura é da seguinte forma:

class property(fget=None,fset=None,fdel=None,doc=None)

Com isso, sabemos que nossas funções de get e set podemos colocá-las nos parâmetros da property e assim acessar da maneira pythônica os atributos ( como se fossem públicos ) , mas internamente sendo feito algum tratamento ..

Lembrando: Você não deve colocar property para todos os seus atributos ... se eles não precisam validar algum valor ou retornar algo formatado não há necessidade de fazer todo esse overhead para somente ter esse arcabouço de ferramentas. Sempre bom senso.

Um exemplo usando o Cupom passado seria da seguinte forma:

class Cupom:

    def __init__(self,nome,desconto):
        self._nome = nome
        self._desconto = desconto

    def getNome(self):
        return self._nome.upper()

    nome = property(fget=getNome)

E na aplicação:

>>> cupom = Cupom("viva10",10)
>>> cupom.nome
VIVA10

Foi usado um underscore por convenção no início do atributo nome para indicar que ele não será usado na sua forma integral na api da classe que será exposta para terceiros... ele passará por alguns processamentos antes de sua exposição.

Está mais pythônico, mas ainda sim tem aqueles get's ali né... A nossa segunda forma de usar as property é usando-as como um decorator.. Usando-as dessa forma basicamente será uma anotação acima de uma função que se comportará como um novo atributo da classe, e internamente terá todo o processamento.

Usando-as como decorators temos as seguintes possibilidades:

@property
@property.getter
@property.setter
@property.deleter

Cada uma indicando qual é a forma de acesso ao atributo. O primeiro decorator será usado para definir qual atributo será "mapeado" para uma property.

Passos para criar uma property

  • Anotar um método qualquer com a anotação @property e nesse método retornar o atributo que deseja ser tratado.
  • Se necessário criar as outras anotações de definição ou obtenção do atributo. Essas anotações ( getter , setter e deleter) deverão ser usadas com relação à property criada.

Vamos à um exemplo.

Dessa vez vou mostrar que cada operação sobre um atributo anotado com property é capturado internamente pelo Python, assim podemos tratá-los como bem entendermos.

class Cupom:

    def __init__(self,nome,desconto):
        self._nome = nome
        self._desconto = desconto

    @property
    def nome(self):
        print("Obtendo o valor do atributo nome")
        return self._nome

    @nome.setter
    def nome(self,valor_do_nome_recebido):
        print("Capturando a definição de um atributo")
        self._nome = valor_do_nome_recebido.upper()

    # Se necessário haveria para os outros atributos

Na aplicação

>>>c = Cupom("viva10",10)
>>> print(c.nome)
Obtendo o valor do atributo nome
viva10
>>> c.nome = "cupom10"
Capturando a definição de um atributo
>>> pint(c.nome)
Obtendo o valor do atributo nome
CUPOM10

Repare que no setter foi referenciado a property criada anteriormente. Sempre que for usar uma operação de property deverá ser com relação à uma property nomeada anteriormente e que mapeia seu acesso para um atributo da classe.

Nesse ponto do nosso conhecimento de properties temos pleno controle sobre nossos dados, tanto sobre o que entra como definição, quanto o que sai como processamento dos nossos dados... e tudo isso sem deixar de lado a simplicidade da sintaxe externa do Python que é o melhor...todo o processsamento ocorrendo internamente, sem o cliente ter conhecimento interno da nossa implementação.

Para um exemplo mais prático vamos fazer com que não seja possível definir um desconto em formato de string, os formatos permitidos serão somente int ou float , logicamente. Vamos lá.

class Cupom:

    def __init__(self,nome,desconto):
        self._nome = nome.upper()
        self._desconto = desconto

    @property
    def desconto(self):
        return self._desconto

    @desconto.setter
    def desconto(self,valor_desconto):
        if not isinstance(valor_desconto,(int,float)):
            raise ValueError("Valor inválido, somente inteiros ou decimais")
        self._desconto = valor_desconto

E na aplicação:

>>> c = Cupom("viva10",10)
>>> print(c.desconto)
10
>>> c.desconto = "10"
Traceback (most recent call last):
  File "property.py", line 20, in <module>
    c.desconto = "cupom10"
  File "property.py", line 14, in desconto
    raise ValueError("Valor inválido, somente inteiros ou decimais")
ValueError: Valor não permitido, somente inteiros ou decimais

E voilà... mantemos nossa API simples para quem usará, e ao mesmo tempo fizemos um encapsulamento adequado do que ocorre dentro da nossa classe, assim escondendo detalhes de implementação.

E é isso pessoal , o uso de property e conseqüentemente como fazer o encapsulamento adequado em OO-Python.

Até o próximo post... Se quiserem sugerir algum tópico só comentar.

Marlysson Silva

Graduando em Análise e Desenvolvimento de Sistemas , programador interessado nas vastas possibilidades de desenvolvimento, seja web(frontend ou backend) ou envolvendo cálculos de processamento. Além de desenvolvimento web tenho interesse na junção informática + processamento , seja usando algoritmos elementares ou usando o auxílio da matemática e estatística, hora ou outra postarei algo a respeito :D

Piauí - Brasil Github Facebook Twitter

Comentários