Pular para o conteúdo

    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.
    intermediario/cap29_encapsulamento.pylinhas 10 a 23
    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)
    
    Saída
    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:

    intermediario/cap29_encapsulamento.pylinhas 28 a 49
    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)
    
    Saída
    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:

    intermediario/cap29_encapsulamento.pylinhas 54 a 69
    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)
    
    Saída
    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.