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.
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
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:
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
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:
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
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.