Capítulo 29, Intermediário
Encapsulamento e @property
Python não tem atributos privados de verdade. Tem convenções, e uma ferramenta, a `@property`, que permite começar simples e proteger depois.
Código deste capítulo: intermediario/cap29_encapsulamento.py
Convenções, não cadeados
Em Python, o controle de acesso é uma convenção entre adultos:
titular: público, qualquer um pode usar._saldo: com um sublinhado, significa "uso interno, não mexa se não for da classe".__pin: com dois sublinhados, o Python faz o name mangling: renomeia o atributo para_Classe__pin, para evitar colisão de nomes em subclasses.
class Conta:
def __init__(self):
self.titular = "Ana"
self._saldo = 100
self.__pin = 1234
conta = Conta()
print(conta.titular, conta._saldo)
try:
conta.__pin
except AttributeError as erro:
print(erro)
print(conta._Conta__pin)
Ana 100
'Conta' object has no attribute '__pin'
1234
O __pin não está protegido: está só com outro nome. O objetivo do duplo sublinhado é evitar colisão em herança, não impedir acesso. Quem quer o valor, acha.
property: validação sem mudar a interface
Em Java, escreve-se getSaldo() e setSaldo() desde o primeiro dia. Em Python, você começa com um atributo comum. Se mais tarde precisar validar, troca por uma @property sem mudar nada em quem usa a classe, porque o acesso continua sendo conta.saldo:
class ContaBancaria:
def __init__(self, saldo=0):
self._saldo = saldo
@property
def saldo(self):
return self._saldo
@saldo.setter
def saldo(self, valor):
if valor < 0:
raise ValueError("saldo não pode ser negativo")
self._saldo = valor
c = ContaBancaria(50)
c.saldo = 80
print(c.saldo)
try:
c.saldo = -1
except ValueError as erro:
print(erro)
80
saldo não pode ser negativo
Propriedades calculadas e somente leitura
Uma @property sem setter vira um atributo calculado e somente leitura. É o jeito certo de expor um valor derivado, que nunca fica desatualizado, porque é recalculado a cada leitura:
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
@property
def area(self):
return self.largura * self.altura
r = Retangulo(3, 4)
print(r.area)
try:
r.area = 10
except AttributeError as erro:
print(erro)
12
property 'area' of 'Retangulo' object has no setter
Exercício 1
Temperatura em Celsius e Fahrenheit
Crie Temperatura com a propriedade celsius (que recusa valores abaixo de -273,15) e a propriedade fahrenheit, que lê e também escreve, convertendo.