Excelente pergunta, vou tentar responder, mas se vc sabe o que POO em Java, vai ficar mais fácil. Senão não vale a pena ler...
Quando vemos uma classe em Python escrita com métodos getters e setters, logo percebemos que o programador ainda não "pegou o jeito" de OOP em Python. Getters e setters são muito comuns em outras linguagens de programação porque os atributos normalmente são declarados com escopo privado. Ou seja, quem usa a classe não tem acesso aos atributos do objeto. Por isso criaram os métodos get...() e set...() para "violar" o encapsulamento dos membros privados.
Encapsulamento é uma coisa muito boa. Devemos usá-lo sempre que for útil. Aliás, talvez as classes sejam uma das melhores formas de colocarmos o encapsulamento em prática.
Python, claro, encoraja encapsulamento, mas de outra forma. Para começar, todos os membros de uma classe -- métodos e atributos -- têm visibilidade pública. Isso mesmo. Nada em um objeto fica escondido.
Definimos métodos e dados privados usando uma convenção: um underline no início do nome. Por exemplo, _codigo ao invés de codigo. Parece estranho a princípio, mas funciona muito bem. Afinal, se alguém quiser usar indevidamente um método ou um atributo da classe, estará sabotando o próprio programa. É o programa dele que vai dar errado!
Vou dar uma pausa aqui para justificar essa opção de projeto do Python. Não podemos esquecer o contexto de uso de uma classe. Uma classe nunca será usada por um usuário final diretamente. Ela sempre será usada por um programa, escrito por um programador. Quem usá-la, saberá o que está fazendo. Se não souber, vai ver os erros quando estiver testando o código e vai corrigir.
Quando vemos que tudo pode ser público em uma classe, passamos a gostar da simplicidade. De fato, quase sempre começamos a construir uma classe com todos os métodos e atributos sem o underline no início e só inserimos o underline ali quando fica claro o uso privado desse membro.
Isso pode nos levar a um problema: o cliente da classe pode ficar sabendo demais sobre a implementação e isso não é encapsulamento. Abordaremos a solução para esse problema nos exemplos abaixo.
Agora vamos começar a analisar um exemplo usando uma classe chamada Pessoa. Eu preciso de uma classe que guarde nome e sobrenome de uma pessoa. Mas também quero pegar o nome completo dela. Vamos aos códigos.
Na abordagem convencional, faríamos assim:
class Pessoa:
def __init__(self, nome, sobrenome):
self._nome = nome
self._sobrenome = sobrenome
def get_nome(self):
return self._nome
def set_nome(self, value):
self._nome = value
def get_sobrenome(self):
return self._sobrenome
def set_sobrenome(self, value):
self._sobrenome = value
def get_nome_completo(self):
return "%s %s" % (self._nome, self._sobrenome)'''
Temos getters, setters e um método específico para pegar o nome completo: get_nome_completo().
Como em Python tudo é público, essa classe poderia ser simplificada assim:
class Pessoa:
def __init__(self, nome, sobrenome):
self.nome = nome
self.sobrenome = sobrenome
def get_nome_completo(self):
return "%s %s" % (self.nome, self.sobrenome)
Quando eu precisar pegar o nome da pessoa, simplesmente uso pessoa.nome. Quando eu quiser pegar o nome completo, uso pessoa.get_nome_completo().
Talvez não seja óbvio, mas um detalhe de implementação da classe Pessoa está exposto. E não é o atributo nome. É o método getnome_completo(). Por que? Porque está claro que ele não é um atributo da classe. Ele começa com get e a chamada precisa ter os parênteses no final. Portanto, é um método getter.
Se eu quiser que esse detalhe fique realmente escondido, preciso usar uma property. Veja o exemplo abaixo:
class Pessoa:
def __init__(self, nome, sobrenome):
self.nome = nome
self.sobrenome = sobrenome
@property
def nome_completo(self):
return "%s %s" % (self.nome, self.sobrenome)'
Agora, para você pegar o nome completo, basta usar pessoa.nomecompleto (sem o get inicial e sem os parênteses no final). Para quem usa a classe, usar pessoa.nome ou pessoa.nome_completo ficou igual. Sem olhar o código fonte da classe ninguém sabe como nome_completo foi implementado. Se é um atributo normal ou se é uma property. O decorator @property faz com que um método da classe possa ser acessado como se fosse um atributo. Isso é encapsulamento de verdade!
CONTINUA