Atualizado: 04/11/2024

Encapsulamento de Propriedades, Getters e Setters - Python

Por padrão, os atributos nas classes são públicos, o que significa que podem ser acessados e modificados a partir de qualquer lugar no programa. Por exemplo:

class Person:
    def __init__(self, name, age):
        self.name = name    # define o nome
        self.age = age      # define a idade
                 
    def print_person(self):
        print(f"Nome: {self.name}\tIdade: {self.age}")
         
tom = Person("Tom", 39)
tom.name = "Homem-Aranha"      # altera o atributo name
tom.age = -129                 # altera o atributo age
tom.print_person()             # Nome: Homem-Aranha     Idade: -129

No entanto, isso permite definir valores incorretos para os atributos, como uma idade negativa, o que é indesejável. Surge, então, a necessidade de controlar o acesso aos atributos do objeto.

Essa questão está relacionada ao conceito de encapsulamento. Encapsulamento é uma ideia central da programação orientada a objetos que consiste em ocultar a funcionalidade e evitar o acesso direto a ela.

Python permite definir atributos privados. Para isso, o nome do atributo deve começar com dois sublinhados, como __name. Vamos reescrever o exemplo, tornando name e age atributos privados:

class Person:
    def __init__(self, name, age):
        self.__name = name    # define o nome
        self.__age = age      # define a idade
                  
    def print_person(self):
        print(f"Nome: {self.__name}\tIdade: {self.__age}")
          
tom = Person("Tom", 39)
tom.__name = "Homem-Aranha"    # tenta alterar o atributo __name
tom.__age = -129               # tenta alterar o atributo __age
tom.print_person()             # Nome: Tom     Idade: 39

Mesmo tentando definir novos valores para __name e __age, o método print_person mostra que os valores dos atributos permanecem inalterados.

Isso ocorre porque, ao declarar um atributo com dois sublinhados, como __attribute, Python realmente cria um atributo com um padrão de nome interno _ClassName__attribute. No exemplo acima, os atributos se tornam _Person__name e _Person__age, acessíveis apenas dentro da classe. Se tentarmos modificar esses atributos externamente, não teremos o efeito desejado:

tom.__age = 43

Aqui, é criado um novo atributo __age, sem qualquer relação com self.__age ou, mais precisamente, self._Person__age.

Caso tentemos acessar o atributo recém-criado sem antes tê-lo definido, obteremos um erro de execução:

print(tom.__age)

Ainda assim, a privacidade desses atributos é relativa. Podemos acessar os valores usando o nome completo do atributo:

class Person:
    def __init__(self, name, age):
        self.__name = name    # define o nome
        self.__age = age      # define a idade
                  
    def print_person(self):
        print(f"Nome: {self.__name}\tIdade: {self.__age}")
          
tom = Person("Tom", 39)
tom._Person__name = "Homem-Aranha"    # altera o atributo __name
tom.print_person()                    # Nome: Homem-Aranha   Idade: 39

Métodos de Acesso: Getters e Setters

Para acessar atributos privados, geralmente usamos métodos de acesso. Um getter permite obter o valor de um atributo, enquanto um setter permite defini-lo. Vamos modificar o exemplo anterior, adicionando métodos de acesso:

class Person:
    def __init__(self, name, age):
        self.__name = name    # define o nome
        self.__age = age      # define a idade

    # setter para definir a idade
    def set_age(self, age):
        if 0 < age < 110:
            self.__age = age
        else:
            print("Idade inválida")

    # getter para obter a idade
    def get_age(self):
        return self.__age

    # getter para obter o nome
    def get_name(self):
        return self.__name
     
    def print_person(self):
        print(f"Nome: {self.__name}\tIdade: {self.__age}")
          
tom = Person("Tom", 39)
tom.print_person()  # Nome: Tom  Idade: 39
tom.set_age(-3486)  # Idade inválida
tom.set_age(25)
tom.print_person()  # Nome: Tom  Idade: 25

O getter para idade é implementado assim:

def get_age(self):
    return self.__age

O setter é definido para validar o valor de idade antes de permitir sua alteração:

def set_age(self, age):
    if 0 < age < 110:
        self.__age = age
    else:
        print("Idade inválida")

A intermediação do acesso aos atributos com métodos permite aplicar lógica adicional. Assim, podemos decidir se devemos redefinir a idade dependendo da validade do valor.

Decorador @property

Outro modo de implementar acesso a atributos é usando decorador @property, que tornam o código mais elegante.

Para criar uma propriedade getter, usa-se @property. Para um setter, @nome_da_propriedade.setter. Vejamos o exemplo anterior usando decoradores:

class Person:
    def __init__(self, name, age):
        self.__name = name    # define o nome
        self.__age = age      # define a idade

    # propriedade getter
    @property
    def age(self):
        return self.__age

    # propriedade setter
    @age.setter
    def age(self, age):
        if 0 < age < 110:
            self.__age = age
        else:
            print("Idade inválida")

    @property
    def name(self):
        return self.__name
     
    def print_person(self):
        print(f"Nome: {self.__name}\tIdade: {self.__age}")
          
tom = Person("Tom", 39)
tom.print_person()  # Nome: Tom  Idade: 39
tom.age = -3486     # Idade inválida  (usa o setter)
print(tom.age)      # 39 (usa o getter)
tom.age = 25        # (usa o setter)
tom.print_person()  # Nome: Tom  Idade: 25

Aqui, o setter deve ser definido após o getter, ambos com o nome age. Assim, o acesso ao atributo pode ser feito diretamente por tom.age, tanto para leitura quanto para atribuição.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com