Um pouco sobre Descritores em Python

Falae pessoal! Hoje pela manha, durante uma aula de Python (Oficinas Turing – por Luciano Ramalho), eu tive novamente a oportunidade de entrar em contato com um mecanismo de abstração fantástico da linguagem – os Descritores ou Descriptors em inglês. Não que tenha sido uma total novidade para mim, mas as discussões guiaram a aula para questões delicadas, que podem envolver desde implementações mais simples até as mais avançadas com metaclasses e outros fantasmas, e eu pensei que talvez fosse uma boa oportunidade para escrever o primeiro blog post da minha vida =P

Então vamos lá, o que é um descritor?

Descritor é um atributo de classe que controla a semântica da atribuição e acesso a um atributo na instância da classe*. Entendeu?

É basicamente um mecanismo de encapsulamento, que permite adicionar lógica nas ações de acesso, atribuição e exclusão de atributos dos objetos, com reuso de código e ao mesmo tempo mantendo sua API elegante.

E como eu faço isso?

A tarefa mínima para dar vida a um descritor é implementar o método __get__, assim você terá um descritor somente leitura.

Para usar como exemplo, temos uma classe Livro que possui 2 atributos: título e autor individual. Para o autor individual eu defini uma regra bem besta: somente serão aceitos nomes compostos por, pelo menos, duas partes, para que ao ser acessado o retorno seja: ‘Gustavo Fonseca’ -> ‘FONSECA, Gustavo’.

Exemplo 0 (Descritor Somente Leitura): código aqui!


class Autor(object):
    def __init__(self, nome_atr):
        self.nome_atr = '_'+nome_atr

    def __get__(self, instancia, classe):
        desmontado = getattr(instancia, self.nome_atr).split()
        sobrenome = desmontado[-1]
        restante = ' '.join(desmontado[:-1])
        return '%s, %s' % (sobrenome.upper(), restante)

class Livro(object):
    autor_individual = Autor('autor_individual')

    def __init__(self, titulo, autor_individual):
        self.titulo = titulo
        if len(autor_individual.split()) < 2:
            raise ValueError('Deve ser informado o nome e sobrenome')
        self._autor_individual = autor_individual

Note que:

  • Descritores são atributos de classe
  • O valor deve ser atribuído na instância
  • O nome do atributo está sendo passado como argumento na instanciação do descritor (bizarro né? mas o descritor precisa de um nome para usar como identificador para o atributo que ele vai definir na instância, por hora vamos fazer assim ok?)

Legal, agora a gente pode criar um novo papel de autor, por exemplo “Autor Organizador” e, com o descritor, já teríamos toda essa lógica de formatação implementada. Mas teríamos que implementar novamente o trecho que trata da atribuição no __init__. Então vamos transformar o descritor em leitura e escrita:

Exemplo 1 (Descritor Leitura/Escrita): código aqui!


class Autor(object):
    def __init__(self, nome_atr):
        self.nome_atr = '__'+nome_atr

    def __set__(self, instancia, valor):
        if len(valor.split()) < 2:
            raise ValueError('Deve ser informado o nome e sobrenome')

        setattr(instancia, self.nome_atr, valor)

    def __get__(self, instancia, classe):
        desmontado = getattr(instancia, self.nome_atr).split()
        sobrenome = desmontado[-1]
        restante = ' '.join(desmontado[:-1])
        return '%s, %s' % (sobrenome.upper(), restante)

class Livro(object):
    autor_individual = Autor('autor_individual')

    def __init__(self, titulo, autor_individual):
        self.titulo = titulo
        self.autor_individual = autor_individual

Note que:

  • Todas as notas do exemplo anterior ainda estão valendo
  • Está bem feio esse nome do atributo sendo passado na instanciação do descritor, ein!

Então agora vamos resolver essa questão da passagem do nome do atributo:

Exemplo 2 (Descritor Leitura/Escrita sem passar o nome do atributo): código aqui!


class Autor(object):
    def __set__(self, instancia, valor):
        if len(valor.split()) < 2:
            raise ValueError('Deve ser informado o nome e sobrenome')

        for nome_atr, valor_atr in instancia.__class__.__dict__.items():
            if valor_atr == self:
                self.nome_atr = '__'+nome_atr
                setattr(instancia, self.nome_atr, valor)
                break

    def __get__(self, instancia, classe):
        desmontado = getattr(instancia, self.nome_atr).split()
        sobrenome = desmontado[-1]
        restante = ' '.join(desmontado[:-1])
        return '%s, %s' % (sobrenome.upper(), restante)

class Livro(object):
    autor_individual = Autor()

    def __init__(self, titulo, autor_individual):
        self.titulo = titulo
        self.autor_individual = autor_individual

Para entender este exemplo é necessário se aprofundar um pouco mais no assunto. O método __set__ de um descritor recebe os argumentos:

  • self: uma referência à instância do próprio descritor, no caso uma instância de Autor
  • instancia: a instância no qual o descritor foi definido, no caso uma instância de Livro
  • valor: o valor à ser atribuído

O que estamos fazendo é iterar sobre os atributos da classe (haja vista que descritores são atributos da classe), em busca do que possui a instância do descritor como valor.  Quando achamos o cara, pegamos o nome do atributo e definimos, na instância, um atributo privado com o seu nome. Entendeu? =]

* Tradução livre da definição existente no livro Python in a Nutshell 2 ed. – Alex Martelli.
Espero ter ajudado! Até mais.

About these ads

9 Comentários on “Um pouco sobre Descritores em Python”

  1. Mandou muito bem, Gustavo! Gostei da solução de resolver onde armazenar o valor na hora de fazer __set__.

  2. Só um comentário: formatar o dado não deve ser função de um descriptor. Entendo que é um exemplo didático, mas achei importante comentar que descriptors estão intimamente ligados ao modelo de dados, e misturar formatação neste nível não é mesmo uma boa idéia.

    Mas a idéia de buscar o nome do atributo no momento de fazer o __set__ foi genial, parabéns mesmo!

  3. rafaelbuy disse:

    “Just another forker”… :)

    Ficou legal o blog Gus, ficaram bons os exemplos e gostei da forma descontraída do post. E cara, o chefe disse que tá genial, mandou bem…

    Continue escrevendo.

    Abraço.

  4. Ivan disse:

    Muito bom! Foi de grande valia pra mim e tenho certeza que será pra outros tb.

  5. Boa Gus, muito bom o post. É esse tipo de artigo que faz um blog ser bom de ler.


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.