Solucionado (ver solução)
Solucionado
(ver solução)
3
respostas

pq usa @property em email e não em nome?

Já que o usuario tbm tem nome pq o @property só é usado no email e não em nome?

class Perfil(models.Model):
    nome = models.CharField(max_length=255, null=False)
    telefone = models.CharField(max_length=15, null=True)
    nome_empresa = models.CharField(max_length=255, null=True)

    contatos = models.ManyToManyField('self')

    usuario = models.OneToOneField(User, related_name='perfil')

    @property
    def email(self):
        return self.usuario.email
usuario = User.objects.create_user(dados_form['nome'], dados_form['email'], dados_form['senha'])
3 respostas

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

PARTE2:

Mas eu posso querer mais detalhes na implementação da classe Pessoa. Por exemplo, o atributo nome precisa começar com uma letra maiúscula. Como fazer isso se estou acessando pessoa.nome diretamente? Simples. Basta criarmos outra property. Agora, para o nome:

class Pessoa:

    def __init__(self, nome, sobrenome):
        self.nome = nome
        self.sobrenome = sobrenome

    @property
    def nome(self):
        return self._nome

    @nome.setter
    def nome(self, value):
        self._nome = value.capitalize()

    @property
    def nome_completo(self):
        return "%s %s" % (self.nome, self._sobrenome)

Para usar essa classe, vamos trabalhar com um exemplo no TERMINAL do Python:

>>> a = Pessoa(nome="felipe", sobrenome="da silva")

>>> a.nome
Felipe
>>> a.nome_completo
Felipe da silva

>>> a.nome = "roberto"
>>> a.nome
Roberto
>>> a.nome_completo
Roberto da silva

>>> a.nome_completo = "qualquer coisa"
...
AttributeError: can't set attribute

Note os detalhes dessa implementação:

O nome da pessoa está guardado no atributo _nome. E isso fica completamente encapsulado dentro da property nome. Podemos usar nome como se fosse um atributo simples, tanto para pegar o valor, quanto para atribuir um valor. O atributo sobrenome está sendo acessado diretamente, pois não temos nenhuma regra especial para ele. O nome completo é uma property read only. Se não usássemos uma property, precisaríamos criar um método set_nome(), com o mesmo resultado. Mas havemos de concordar que a sintaxe da property é muito mais intuitiva para quem usa a classe, não é?

No futuro, se tivermos alguma particularidade para o atributo sobrenome, podemos mudar a implementação da classe sem afetar nenhum código externo, simplesmente usando outra property; dessa vez para o sobrenome.

As properties são muitos úteis quando utilizadas em lugar de métodos que retornam algum estado da classe. Por exemplo:

property is_active ao invés de um método is_active() is_valid ao invés de is_valid() nome_completo ao invés de get_nome_completo() contatos ao invés de get_contatos()

solução!

PARTE3: Vejamos agora um caso real e bem simples onde eu uso properties para retornar estados da minha própria classe de exception.

'''class MinhaException(Exception):

@property def code(self): return self.args[0]

@property def message(self): return self.args[1]

try: raise MinhaException("003", "Campo obrigatório") except MinhaException as e: print ("Erro %s (%s)" % (e.code, e.message))'''

O exemplo acima cria duas properties, code e message, para abstrair a forma como esse atributos são guardados pela classe Exception. MinhaException simplifica o acesso a esses atributos ao mesmo tempo que aproveita a infraestrutura do Python, sem criar nada desnecessário.

Properties podem fazer mais do que isso. No exemplo abaixo temos dois usos poderosos de properties sem perder a simplicidade: a) "apagar" um atributo; b) uma property que é write only:

>>> class Campo:
...     def __init__(self, valor):
...         self.valor = valor
...         self._erros = []
...
...     @property
...     def erros(self):
...         return self._erros
...
...     @erros.deleter
...     def erros(self):
...         self._erros = []
...
...     def _set_erro(self, valor):
...         self._erros.append(valor)
...
...     erro = property(fset=_set_erro)
...
>>>
>>> c = Campo("nome")
>>> c.erro = "required"
>>> c.erro = "too short"
>>> c.erros
['required', 'too short']
>>> del c.erros
>>> c.erros
[]

Percebemos que o comando del c.erros chama a property @erros.deleter para "apagar" o conteúdo de _erros. Na verdade, _erros é inicializado novamente e o objeto continua com o atributo funcionando. Muito melhor do que se usásssemos clean_erros(), não é?

Note que se tentarmos fazer c.erros = ["a", "b"], vamos ver uma mensagem dizendo que ele não pode ser ajustado. Esse comportamento é proposital. Novamente, não vamos expor os detalhes de implementação da classe. Então, usamos uma property que pode apenas receber valor, o atributo erro. Usamos c.erro para adicionar um erro à lista. Bem mais intuitivo do que algo como add_erro(). Nesse caso precisamos usar a função builtin property() porque esse atributo não pode ser lido.

Concluindo, as properties são melhores do que métodos getters e setters porque:

Podemos iniciar uma classe de uma forma bem direta, sem complexidade desnecessária, usando atributos simples; Podemos acrescentar regras de armazenamento e/ou fornecimento de informações à medida do necessário, sem mexer na interface da classe; A sintaxe de uso das properties é igual ao uso de atributos, sendo mais intuitiva para os clientes da classe. Não escreva mais suas classes com sotaque de outras linguagens. Use properties ao invés de getters e setters.

FONTES:

1) https://www.hardware.com.br/artigos/programacao-orientada-objetos/

2) http://blog.aprendapython.com.br/articles/pq-properties-sao-melhores-que-getters-and-setters-1fr72/

3) https://novatec.com.br/livros/introducao-orientacao-objetos/

4) http://online.k19.com.br/libraries/handouts/k11