Skip to content

    Chapter 29, Intermediate

    Encapsulation and @property

    Python does not have truly private attributes. It has conventions, and a tool, the `@property`, which allows you to start simple and protect later.

    Code for this chapter: intermediario/cap29_encapsulamento.py

    Conventions, not locks

    In Python, access control is a convention among adults:

    • titular: public, anyone can use it.
    • _saldo: with a single underscore, means "internal use, do not touch unless you are from the class".
    • __pin: with double underscores, Python does the name mangling: renames the attribute to _Classe__pin, to avoid name collisions in 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)
    
    Output
    Ana 100
    'Conta' object has no attribute '__pin'
    1234
    

    The __pin is not protected: it just has another name. The goal of the double underscore is to avoid collisions in inheritance, not to prevent access. Whoever wants the value, finds it.

    property: validation without changing the interface

    In Java, you write getSaldo() and setSaldo() from day one. In Python, you start with a common attribute. If you later need to validate, you replace it with a @property without changing anything for those who use the class, because access remains 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)
    
    Output
    80
    saldo não pode ser negativo
    

    Calculated and read-only properties

    A @property without a setter becomes a calculated and read-only attribute. It's the right way to expose a derived value, which is never outdated, because it is recalculated with each read:

    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)
    
    Output
    12
    property 'area' of 'Retangulo' object has no setter
    

    Exercise 1

    Temperature in Celsius and Fahrenheit

    Create Temperatura with the property celsius (which rejects values below -273.15) and the property fahrenheit, which reads and also writes, converting.