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
name
age
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
__age
print_person
Isso ocorre porque, ao declarar um atributo com dois sublinhados, como __attribute
_ClassName__attribute
_Person__name
_Person__age
tom.__age = 43
Aqui, é criado um novo atributo __age
self.__age
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
Para criar uma propriedade getter, usa-se @property
@nome_da_propriedade.setter
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
tom.age