Modificadores De Acesso E Métodos De Classe

Um dos problemas mais simples que temos no nosso sistema de contas é que o método saca() permite sacar mesmo que o saldo seja insuficiente. A seguir, você pode lembrar como está a classe Conta :

class Conta:
python
def __init__(self, numero, titular, saldo, limite=1000.0): self.numero = numero
 
self.titular = titular self.saldo = saldo self.limite = limite
 
# outros métodos
 
python
def saca(self, valor): self.saldo -= valor

Vamos testar nosso código:

minha_conta = Conta(‘123-4’, ‘João’, 1000.0, 2000.0) minha_conta.saca(500000)

O limite de saque é ultrapassado. Podemos incluir um if dentro do método saca() para evitar a situação que resultaria em uma conta em estado inconsistente, com seu saldo menor do que zero. Fizemos isso no capítulo de orientação a objetos básica.

Apesar de melhorar bastante, ainda temos um problema mais grave: ninguém garante que o usuário da classe vai sempre utilizar o método para alterar o saldo da conta. O código a seguir altera o saldo diretamente:

minha_conta = Conta(‘123-4’, ‘João’, 1000.0) minha_conta.saldo = -200

Como evitar isso? Uma ideia simples seria testar se não estamos sacando um valor maior que o saldo toda vez que formos alterá-lo.

minha_conta = Conta(‘123-4’, ‘joão’, 1000.0) novo_saldo = -200

if (novo_saldo < 0): print("saldo inválido")
java
else:
 
minha_conta.saldo = novo_saldo
 
Esse código iria se repetir ao longo de toda nossa aplicação, ou pior, alguém pode esquecer de fazer essa comparação em algum momento, deixando a conta em uma situação inconsistente. A melhor forma de resolver isso seria forçar quem usa a classe Conta a invocar o método saca() , para assim não permitir o acesso direto ao atributo.
 

Em linguagens como Java e C#, basta declarar que os atributos não possam ser acessados de fora da classe utilizando a palavra chave private. Em orientação a objetos, é prática quase que obrigatória proteger seus atributos com private. Cada classe é responsável por controlar seus atributos, portanto ela deve julgar se aquele novo valor é válido ou não. E esta validação não deve ser controlada por quem está usando a classe, e sim por ela mesma, centralizando essa responsabilidade e facilitando futuras mudanças no sistema.

O Python não utiliza o termo private, que é um modificador de acesso e também chamado de modificador de visibilidade. No Python, inserimos dois underscores (’ ’) ao atributo para adicionarmos esta característica:

class Pessoa:
python
def __init__(self, idade): self. idade = idade
 

Dessa maneira, não conseguimos acessar o atributo idade de um objeto do tipo Pessoa fora da classe:

pessoa = Pessoa(20) pessoa.idade

Traceback (most recent call last): File "<stdin>", line 1, in <module>
 
AttributeError: 'Pessoa' object has no attribute 'idade'
 

O interpretador acusa que o atributo idade não existe na classe Pessoa . Mas isso não garante que ninguém possa acessá-lo. No Python, não existem atributos realmente privados, ele apenas alerta que você não deveria tentar acessar este atributo, ou modificá-lo. Para acessá-lo, fazemos:

p._Pessoa idade

Ao colocar o prefixo no atributo da classe, o Python apenas renomeia ’ nome_do_atributo’ para ‘_nome*da_Classe*_nome_do_atributo’, como fez em idade para _Pessoa idade. Qualquer pessoa que saiba que os atributos privados não são realmente privados, mas “desconfigurados”, pode ler e atribuir um valor ao atributo “privado” diretamente. Mas fazer pessoa._Pessoa idade = 20 é considerado má prática e pode acarretar em erros.

Podemos utilizar a função dir para ver que o atributo _Pessoa idade pertence ao objeto:

dir(pessoa)

[‘_Pessoa idade’, ’ class ’, ’ delattr ’, ’ dict ’, ’ dir ’,

’ doc ’, ’ eq ’, ’ format ’, ’ ge ’, ’ getattribute ’, ’ gt ’, ’ hash ’, ’ init ’, ’ init_subclass ’, ’ le ’, ’ lt ’,

’ module ’, ’ ne ’, ’ new ’, ’ reduce ’, ’ reduce_ex ’,

’ repr ’, ’ setattr ’, ’ sizeof ’, ’ str ’, ’ subclasshook ’, ’ weakref ’]

Repare que não existe nenhum atributo idade no objeto pessoa. Agora vamos tentar atribuir um valor para idade :

pessoa. idade =

Epa, será que o Python deveria deixar isso ocorrer? Vamos acessar a variável novamente e ver se a modificação realmente aconteceu:

print(pessoa._Pessoa idade) #20

O que aconteceu aqui é que o Python criou um novo atributo idade para o objeto pessoa já que é uma linguagem dinâmica. Vamos utilizar a função dir novamente para ver isso:

dir(pessoa)

[‘_Pessoa idade’, ’ class ’, ’ delattr ’, ’ dict ’, ’ dir ’,

’ doc ’, ’ eq ’, ’ format ’, ’ ge ’, ’ getattribute ’, ’ gt ’, ’ hash ’, ’ idade’, ’ init ’, ’ init_subclass ’, ’ le ’, ’ lt_’ ’ module ’, ’ ne ’, ’ new ’, ’ reduce ’, ’ reduce_ex ’,

’ repr ’, ’ setattr ’, ’ sizeof ’, ’ str ’, ’ subclasshook ’, ’ weakref ’]

Note que um novo atributo idade apareceu, já que foi inicializado em tempo de execução e é diferente do idade da classe. Isso pode gerar muita confusão e erros! O Python também tem uma maneira de lidar com este problema através da variável slots, onde definimos um número limitado de atributos que veremos a seguir.

Nenhum atributo é realmente privado em Python, já que podemos acessá-lo pelo seu nome ‘desfigurado’. Muitos programadores Python não gostam dessa sintaxe e preferem usar apenas um underscore ’_’ para indicar quando um atributo deve ser protegido. Ou seja, deve ser explícita essa desconfiguração do nome - feita pelo programador e não pelo interpretador - já que oferece o mesmo resultado. E argumentam que ’ ’ são obscuros.

O prefixo com apenas um underscore não tem significado para o interpretador quando usado em nome de atributos, mas entre programadores Python é uma convenção que deve ser respeitada. O programador alerta que esse atributo não deve ser acessado diretamente:

def __init__(self, idade): self._idade = idade
 

Um atributo com apenas um underscore é chamado de protegido, mas quando usado sinaliza que deve ser tratado como um atributo “privado”, fazendo com que acessá-lo diretamente possa ser perigoso.

As mesmas regras de acesso aos atributos valem para os métodos. É muito comum, e faz todo sentido, que seus atributos sejam privados e quase todos seus métodos sejam públicos (não é uma regra!). Desta forma, toda conversa de um objeto com outro é feita por troca de mensagens, isto é,

acessando seus métodos. Algo muito mais educado que mexer diretamente em um atributo que não é seu.‌

Melhor ainda! O dia que precisarmos mudar como é realizado um saque na nossa classe Conta , adivinhe onde precisaríamos modificar? Apenas no método saca() , o que faz pleno sentido.


⬅️ Capítulo Anterior | Próximo Capítulo ➡️