Trilha de aprendizado · Nível 11 · Tutorial 9

Reutilizar controle de atributos com descritores

Ao concluir, você poderá implementar um descritor reutilizável para controlar atributos de várias classes e explicar sua participação na resolução de atributos.

  • Nível: Avançado
  • Duração: 25 min
  • 8 passos
Reutilizar controle de atributos com descritores

O que você vai percorrer

  1. Identificar a lógica de atributos que pode ser reutilizada Reconheça quando uma regra de validação deixa de pertencer a um único atributo e pode ser concentrada em um descritor reutilizável. 2 min
  2. Controlar a leitura com __get__ Implemente a leitura controlada de um atributo e diferencie o acesso feito por uma instância daquele feito pela classe. 3 min
  3. Validar atribuições sem compartilhar os valores Implemente __set__ para validar textos antes de gravá-los e mantenha cada valor no dicionário da respectiva instância. 4 min
  4. Associar nomes automaticamente com __set_name__ Use __set_name__ para derivar a chave interna de cada atributo e reutilizar um descritor em campos e classes diferentes. 3 min
  5. Controlar a remoção e classificar os descritores Defina uma política explícita para del e identifique descritores de dados e de não dados pelos métodos do seu tipo. 3 min
  6. Prever qual valor vence na leitura Entenda a ordem usual de leitura de atributos por instância e preveja conflitos entre descritores e valores no dicionário do objeto. 4 min
  7. Relacionar descritores a property e métodos Reconheça descritores em recursos conhecidos e escolha entre uma property e um descritor próprio conforme o ganho de reutilização. 2 min
  8. Aplicação final: reutilizar e verificar o descritor Integre um descritor reutilizável, execute verificações locais e justifique quando ele oferece vantagem sobre properties específicas. 4 min

O que você vai aprender

  • Implementar um descritor com leitura e validação de atribuições.
  • Usar __set_name__ para identificar o atributo ao qual o descritor foi associado.
  • Manter os valores separados por instância, sem armazená-los no descritor compartilhado.
  • Prever a precedência de descritores de dados e de não dados em relação aos atributos da instância.
  • Escolher um descritor somente quando houver ganho de reutilização em relação a property.

Antes de começar

  • Controlar atributos com property
  • Definir métodos de classe e métodos estáticos
  • Distinguir atributos de instância e de classe
  • Preservar regras de validade em objetos

Passo 1 de 8

Identificar a lógica de atributos que pode ser reutilizada

Reconheça quando uma regra de validação deixa de pertencer a um único atributo e pode ser concentrada em um descritor reutilizável.

Uma regra repetida em vários campos

Validação que se repete

Imagine que Produto.nome, Produto.marca e Categoria.titulo precisem aceitar apenas textos com um comprimento mínimo — mas cada campo pode exigir um mínimo diferente.

Copiar essa regra em vários setters torna a manutenção repetitiva. Um descritor permite concentrar a política em um objeto reutilizável, configurado para cada atributo.

Uma regra, vários atributos

A regra reutilizável fica associada às classes que declaram os atributos; os objetos consumidores usam essa regra ao acessar seus campos.

Diagrama mostrando objetos descritores configurados ligados aos atributos nome e marca da classe Produto e ao atributo titulo da classe Categoria, com instâncias de cada classe abaixo.

Cada atributo declarado recebe um objeto descritor; a classe do descritor pode ser reutilizada em campos e classes diferentes.

Quem é quem na declaração?

Três papéis distintos

Veja uma declaração ainda incompleta — a implementação do protocolo virá nos próximos passos:

  • TextoMinimo é a classe do descritor: ela define a regra reutilizável.
  • TextoMinimo(3) cria um objeto descritor, configurado com mínimo 3.
  • Produto é a classe consumidora: ela declara o atributo controlado, nome.

Declaração de um descritor na classe

Exemplo estrutural: o comportamento de TextoMinimo será implementado nos próximos passos.

python
class TextoMinimo:
    def __init__(self, minimo):
        self.minimo = minimo


class Produto:
    nome = TextoMinimo(3)
    marca = TextoMinimo(2)


class Categoria:
    titulo = TextoMinimo(5)

Dica

Local da associação

O descritor é associado no corpo da classe consumidora, como nome = TextoMinimo(3). Não basta colocá-lo no __dict__ de uma instância específica: assim a regra não participa do acesso ao atributo declarado pela classe.

Identifique a associação correta

Cheque seu entendimento

No código apresentado, qual alternativa identifica corretamente os papéis de nome = TextoMinimo(3) dentro de Produto?

Passo 2 de 8

Controlar a leitura com __get__

Implemente a leitura controlada de um atributo e diferencie o acesso feito por uma instância daquele feito pela classe.

Dois caminhos de leitura

Quem recebe o acesso?

Quando um atributo controlado por descritor é lido, Python chama __get__(self, instance, owner).

  • self: o próprio objeto descritor.
  • instance: a instância acessada, como produto, ou None no acesso pela classe.
  • owner: a classe envolvida no acesso, como Produto.

Assim, produto.nome e Produto.nome podem seguir caminhos diferentes, mesmo apontando para o mesmo atributo declarado na classe.

Fluxo de __get__

O valor público nome pode ser lido de uma chave interna, como _nome, sem expor essa convenção na interface da classe.

Diagrama com dois caminhos de acesso a um descritor: pela instância, o descritor consulta uma chave interna no dicionário da instância; pela classe, o descritor retorna a si mesmo.

Pela instância, a leitura busca o valor interno. Pela classe, instance é None e o descritor pode ser devolvido.

Implementar uma leitura controlada

Armazenamento e ausência de valor

Em instâncias comuns, instance.__dict__ é um dicionário que pode guardar atributos. Aqui, o descritor lê somente a chave interna _nome.

Não é correto supor que todo objeto Python tenha __dict__: classes com certas configurações podem não oferecê-lo. Neste exemplo didático, o descritor foi escolhido para objetos comuns que o possuem.

Se a chave interna não existir, converta o KeyError do dicionário em AttributeError. Para quem usa o atributo, o resultado é coerente: o atributo ainda não está disponível.

Descritor de leitura

Teste este código em um arquivo Python. A chave interna é configurada manualmente por enquanto; a associação automática de nomes será tratada depois.

python
class LeitorDeTexto:
    def __init__(self, chave_interna):
        self.chave_interna = chave_interna

    def __get__(self, instance, owner):
        if instance is None:
            return self

        try:
            return instance.__dict__[self.chave_interna]
        except KeyError:
            raise AttributeError(
                f"{owner.__name__} ainda não tem {self.chave_interna!r}"
            ) from None


class Produto:
    nome = LeitorDeTexto("_nome")


produto = Produto()
produto.__dict__["_nome"] = "Caderno"

print(produto.nome)       # Caderno
print(Produto.nome)       # objeto LeitorDeTexto

vazio = Produto()
try:
    print(vazio.nome)
except AttributeError as erro:
    print(erro)           # Produto ainda não tem '_nome'

Verifique os acessos

Relacione acesso e comportamento

Considere a classe Produto e o descritor LeitorDeTexto do exemplo. Relacione cada situação ao comportamento de __get__.

Toque em um item e depois no par correspondente.

Passo 3 de 8

Validar atribuições sem compartilhar os valores

Implemente set para validar textos antes de gravá-los e mantenha cada valor no dicionário da respectiva instância.

Validação antes do armazenamento

O papel de __set__

Quando você executa objeto.nome = valor, o método __set__(self, instance, value) do descritor controla essa atribuição.

No nosso contrato, TextoMinimo aceita somente valores do tipo str com o comprimento mínimo configurado. Não converta valores implicitamente: um número como 123 deve causar TypeError, e uma string curta demais deve causar ValueError.

A ordem importa: valide tudo antes de gravar. Assim, se a atribuição falhar, o valor que a instância já possuía permanece intacto.

Uma regra, valores separados

O descritor é compartilhado pela classe, mas cada objeto consumidor guarda seu próprio valor na chave interna.

Diagrama comparando armazenamento incorreto de um valor único no descritor com armazenamento correto de valores separados nos dicionários de duas instâncias.

À esquerda, guardar value no descritor faz uma instância sobrescrever a outra. À direita, a configuração fica no descritor e cada valor fica em instance.__dict__.

Implementação com chave interna fixa

Descritor TextoMinimo

Execute este exemplo em um arquivo Python no seu computador.

python
class TextoMinimo:
    def __init__(self, minimo):
        self.minimo = minimo
        self.chave_interna = "_nome"

    def __get__(self, instance, owner):
        if instance is None:
            return self
        try:
            return instance.__dict__[self.chave_interna]
        except KeyError:
            raise AttributeError("nome ainda não foi definido") from None

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("nome deve ser uma string")
        if len(value) < self.minimo:
            raise ValueError(
                f"nome deve ter ao menos {self.minimo} caracteres"
            )
        instance.__dict__[self.chave_interna] = value


class Pessoa:
    nome = TextoMinimo(3)


ana = Pessoa()
bruno = Pessoa()

ana.nome = "Ana"
bruno.nome = "Bruno"

print(ana.nome)       # Ana
print(bruno.nome)     # Bruno
print(ana.__dict__)   # {'_nome': 'Ana'}
print(bruno.__dict__) # {'_nome': 'Bruno'}

try:
    ana.nome = "Li"
except ValueError as erro:
    print(erro)

print(ana.nome)       # Ana: o valor anterior foi preservado

try:
    bruno.nome = 42
except TypeError as erro:
    print(erro)

print(bruno.nome)     # Bruno: o valor anterior foi preservado

Atenção

Evite recursão no setter

Dentro de __set__, não escreva instance.nome = value. Essa linha acionaria o mesmo __set__ novamente, causando recursão até um erro.

Grave diretamente na chave interna: instance.__dict__[self.chave_interna] = value. Neste exemplo, "_nome" ainda é fixa; no próximo passo ela será derivada automaticamente do atributo associado.

Verifique o isolamento e a preservação

Relate o que você observou

Execute o código apresentado. Em sua resposta, informe os valores finais de ana.nome e bruno.nome, indique onde cada valor aparece e explique o que ocorreu após as duas atribuições inválidas.

Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 8

Associar nomes automaticamente com __set_name__

Use set_name para derivar a chave interna de cada atributo e reutilizar um descritor em campos e classes diferentes.

O nome chega na criação da classe

Configuração não é associação

Até aqui, o descritor já guarda sua configuração, como o comprimento mínimo, e os valores ficam em cada instância. Falta descobrir automaticamente qual atributo público ele controla.

Quando Python cria uma classe, ele chama __set_name__(self, owner, name) para cada objeto associado no corpo da classe cujo tipo define esse método:

  • self: o objeto descritor;
  • owner: a classe que o declarou;
  • name: o nome público usado na declaração, como nome.

Assim, minimo vem da construção de TextoMinimo(3), enquanto name vem da associação nome = ... no corpo da classe.

Dois momentos do descritor

A imagem separa a configuração criada antes da classe da associação que ocorre durante sua criação.

Diagrama mostrando um descritor TextoMinimo criado com mínimo 3 e depois associado ao atributo nome da classe Produto; a associação fornece o nome nome e gera a chave interna _nome.

A construção define a regra; a associação à classe fornece o nome do atributo.

Uma chave interna para cada atributo

Descritor reutilizável com nome automático

A chave interna e as mensagens usam o nome recebido em __set_name__. Cada declaração abaixo cria um novo objeto TextoMinimo, ainda que todas usem a mesma implementação.

python
class TextoMinimo:
    def __init__(self, minimo):
        self.minimo = minimo

    def __set_name__(self, owner, name):
        self.nome_publico = name
        self.chave_interna = f"_{name}"

    def __get__(self, instance, owner):
        if instance is None:
            return self

        try:
            return instance.__dict__[self.chave_interna]
        except KeyError:
            raise AttributeError(
                f"{self.nome_publico!r} ainda não foi definido"
            ) from None

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError(f"{self.nome_publico!r} deve ser texto")
        if len(value) < self.minimo:
            raise ValueError(
                f"{self.nome_publico!r} deve ter ao menos {self.minimo} caracteres"
            )

        instance.__dict__[self.chave_interna] = value


class Produto:
    nome = TextoMinimo(3)
    marca = TextoMinimo(2)


class Categoria:
    titulo = TextoMinimo(4)


produto = Produto()
produto.nome = "Caderno"
produto.marca = "AC"

categoria = Categoria()
categoria.titulo = "Papelaria"

print(produto.__dict__)   # {'_nome': 'Caderno', '_marca': 'AC'}
print(categoria.__dict__) # {'_titulo': 'Papelaria'}

Dica

Implementação reutilizada, objetos distintos

Produto.nome, Produto.marca e Categoria.titulo usam a mesma classe de descritor, mas cada expressão TextoMinimo(...) cria um objeto descritor diferente. Isso permite que cada um guarde sua própria associação (nome_publico e chave_interna).

Evite reutilizar o mesmo objeto descritor

Uma associação sobrescreve a outra

Não faça isto quando o descritor guarda apenas um nome e uma chave interna:

texto_curto = TextoMinimo(2)

class Produto:
    nome = texto_curto
    marca = texto_curto

__set_name__ será chamado duas vezes no mesmo objeto. A segunda chamada pode substituir os dados da primeira: ao final, o descritor pode se considerar associado somente a marca. Para este design, crie um objeto descritor por atributo declarado.

Atenção

Associação depois da criação da classe

A chamada automática ocorre durante a criação da classe. Portanto, isto não chama __set_name__ automaticamente:

class Produto:
    pass

Produto.nome = TextoMinimo(3)

Nesse caso, o descritor ainda não recebeu nome_publico nem chave_interna. Para este padrão, declare o atributo no corpo da classe. Tratamentos mais gerais para associações dinâmicas ficam fora deste tutorial.

Confira as associações

Chave interna de marca

No código do exemplo, a gravação em produto.marca usa a chave interna __ em `produto.dict__`.

Escolha o desenho correto

Qual declaração reutiliza a regra sem arriscar sobrescrever a associação de nome no mesmo objeto descritor?

Passo 5 de 8

Controlar a remoção e classificar os descritores

Defina uma política explícita para del e identifique descritores de dados e de não dados pelos métodos do seu tipo.

Remoção também pode ser controlada

Uma política para del

Além de controlar leitura e atribuição, um descritor pode definir o que ocorre em del objeto.atributo por meio de __delete__(self, instance).

No TextoMinimo, a política será remover a chave interna criada em __set_name__. Se não houver valor armazenado, a remoção sinaliza AttributeError, assim como a leitura de um valor indisponível.

Do atributo público ao armazenamento interno

Diagrama mostrando del em um atributo público controlado por descritor, que remove somente a chave interna correspondente no dicionário daquela instância; uma leitura posterior resulta em AttributeError.

A remoção passa pelo descritor e atua na chave interna da instância.

Implementação de __delete__

Acrescente este método à classe TextoMinimo, que já possui self.chave_interna definida em __set_name__.

python
def __delete__(self, instance):
    try:
        del instance.__dict__[self.chave_interna]
    except KeyError:
        raise AttributeError(
            f"{self.nome_publico!r} não possui valor"
        ) from None

Efeito da remoção

Teste no seu computador

Com uma classe Produto que use nome = TextoMinimo(3), execute estas operações na ordem mostrada.

python
produto = Produto()
produto.nome = "Caderno"

print(produto.nome)       # Caderno
del produto.nome

print(produto.__dict__)   # {}
print(produto.nome)       # AttributeError: 'nome' não possui valor

Atenção

Omitir __delete__ não torna del livre

Se o tipo do descritor define __set__, ele já controla atribuições. Apenas deixar de implementar __delete__ não faz com que del objeto.atributo remova automaticamente um valor do armazenamento interno: a operação é rejeitada com AttributeError.

Implemente __delete__ somente quando quiser oferecer uma política de remoção.

Preveja a leitura

Depois de del produto.nome remover a chave interna, uma leitura de produto.nome deve resultar em AttributeError.

A classificação depende dos métodos

Dados ou não dados

A categoria é determinada pelos métodos definidos no tipo do descritor:

  • Descritor de dados: define __set__ ou __delete__ (e pode também definir __get__).
  • Descritor de não dados: define __get__, mas não define __set__ nem __delete__.

Portanto, __set_name__ recebe o nome da associação, mas não participa dessa classificação. Um método que rejeita uma operação ainda conta: se __set__ existe e sempre levanta erro, o descritor continua sendo de dados.

Classificação pelo protocolo implementado

Comparação lado a lado: descritor de dados com get e set ou delete; descritor de não dados apenas com get; set_name apresentado separadamente como método neutro para a classificação.

set_name ajuda a configurar o descritor, mas não decide sua categoria.

Classifique pelo tipo

Três implementações

Considere apenas os métodos declarados em cada classe de descritor.

python
class Leitura:
    def __get__(self, instance, owner):
        return 42


class Gravavel:
    def __get__(self, instance, owner):
        return instance.__dict__["_valor"]

    def __set__(self, instance, value):
        instance.__dict__["_valor"] = value


class Removivel:
    def __delete__(self, instance):
        del instance.__dict__["_valor"]

    def __set_name__(self, owner, name):
        self.nome = name

Classificação correta

Qual classificação está correta para os três tipos do código?

Passo 6 de 8

Prever qual valor vence na leitura

Entenda a ordem usual de leitura de atributos por instância e preveja conflitos entre descritores e valores no dicionário do objeto.

A ordem usual da leitura

Quem tem prioridade?

No acesso usual por instância, como em obj.nome, Python localiza o candidato na classe ou em suas classes-base e aplica uma ordem de precedência. Sem personalizar a busca de atributos, a leitura segue esta sequência:

  1. descritor de dados com __get__;
  2. entrada "nome" em obj.__dict__;
  3. descritor de não dados com __get__;
  4. outro atributo da classe.

Um descritor de dados define __set__ ou __delete__; por isso, ele vence uma chave pública de mesmo nome no dicionário da instância.

Camadas de precedência

A imagem representa a disputa pelo nome público nome. A chave interna usada pelo descritor, como _nome, é outro nome e não participa desse conflito direto.

Diagrama em camadas mostrando descritor de dados, dicionário da instância, descritor de não dados e atributo de classe em ordem de prioridade para a leitura de um atributo.

A busca considera a chave pública em conflito. Uma chave interna, como _nome, é independente.

Dois conflitos, dois resultados

Experimento de precedência

Execute este código no seu computador e compare os valores impressos.

python
class Dado:
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return "valor do descritor de dados"

    def __set__(self, instance, value):
        instance.__dict__["_protegido"] = value


class NaoDado:
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return "valor do descritor de não dados"


class Registro:
    protegido = Dado()
    aberto = NaoDado()


registro = Registro()
registro.__dict__["protegido"] = "tentativa de encobrir"
registro.__dict__["aberto"] = "valor da instância"

print(registro.protegido)
print(registro.aberto)

del registro.__dict__["aberto"]
print(registro.aberto)

Exemplo

Resultado previsto

A saída é:

valor do descritor de dados
valor da instância
valor do descritor de não dados

A chave pública "protegido" existe em registro.__dict__, mas não encobre protegido, pois esse é um descritor de dados. Já "aberto" encobre o descritor de não dados enquanto existir. Após o del, o descritor volta a ser visível.

Dica

Público não é interno

No descritor TextoMinimo, o valor validado deve ficar em uma chave interna, como _nome. Isso evita recursão e também evita confundir esse armazenamento com uma chave pública conflitante, como nome.

Ordene as fontes de leitura

Precedência em obj.nome

Coloque as fontes na ordem usual em que podem fornecer o valor de obj.nome, da maior para a menor precedência.

  1. Entrada "nome" em obj.__dict__
  2. Atributo comum da classe
  3. Descritor de dados com __get__
  4. Descritor de não dados com __get__

O controle não é uma barreira de segurança

Atenção

Não contorne a interface pública

Escrever diretamente em obj.__dict__["_nome"] pode inserir um valor que nunca passou por __set__. Um descritor organiza e reutiliza regras de acesso; ele não impede código que tenha acesso ao objeto de alterar seu armazenamento interno.

Explique o contraste

No exemplo anterior, por que registro.__dict__["protegido"] não vence a leitura de registro.protegido, enquanto registro.__dict__["aberto"] vence temporariamente a leitura de registro.aberto?

Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 8

Relacionar descritores a property e métodos

Reconheça descritores em recursos conhecidos e escolha entre uma property e um descritor próprio conforme o ganho de reutilização.

O protocolo por trás de recursos conhecidos

Property e método também participam do protocolo

Você já usou property e métodos de instância. Ambos são objetos colocados no corpo da classe e participam do acesso a atributos por meio de descritores.

Uma property é um descritor de dados, inclusive se você não declarou um setter. Por isso, uma entrada com o mesmo nome público em obj.__dict__ não a encobre na leitura.

Já uma função definida no corpo da classe é um descritor de não dados: seu __get__ participa da criação do método vinculado quando o acesso ocorre pela instância.

Dois acessos, duas classificações

Compare a precedência aplicada a uma property e a uma função de classe.

Diagrama comparando uma property, classificada como descritor de dados e consultada antes do dicionário da instância, com uma função da classe, classificada como descritor de não dados e consultada depois do dicionário da instância.

Property vence uma chave pública da instância; uma função pode ser encoberta por essa chave.

A precedência explica o comportamento

Exemplo

Mesmo nome público, resultados diferentes

class Conta:
    @property
    def saldo(self):
        return 100

    def resumo(self):
        return "Conta ativa"

conta = Conta()
conta.__dict__["saldo"] = 999
conta.__dict__["resumo"] = "valor armazenado"

print(conta.saldo)   # 100
print(conta.resumo)  # 'valor armazenado'

A chave "saldo" não encobre a property, pois ela é um descritor de dados. A chave "resumo" encobre a função encontrada na classe, pois funções são descritores de não dados.

Essa é uma demonstração da resolução usual de atributos; escrever diretamente em __dict__ contorna interfaces e não é uma prática recomendada para o uso normal do objeto.

Dica

Não confunda leitura e atribuição

Uma property sem setter ainda é descritor de dados. A tentativa normal de atribuir ao atributo é rejeitada; a ausência de um setter público não a transforma em descritor de não dados.

Decisão de projeto: property ou descritor?

Escolha pelo alcance da política

Prefira uma property quando a regra pertence a um atributo específico de uma classe: ela deixa a intenção local e direta.

Crie um descritor próprio quando a mesma política de leitura, validação e armazenamento precisar ser aplicada de modo consistente em vários campos ou em várias classes — como TextoMinimo associado a nomes e descrições com configurações diferentes.

O descritor reduz repetição, mas cobra um custo: configuração, chave interna, entendimento de __set_name__ e conhecimento da resolução de atributos. Reutilização pequena não costuma compensar essa complexidade.

Alcance da regra

A estrutura deve acompanhar a quantidade de lugares que compartilham a regra.

Comparação visual: uma property ligada a um único atributo de uma única classe e um descritor reutilizável ligado a múltiplos atributos em duas classes, mantendo valores separados em cada instância.

Regra local: property. Política repetida: descritor próprio.

Escolha a abstração adequada

Cenário de projeto

A classe Pedido possui total, calculado a partir dos itens. Nenhuma outra classe precisa desse cálculo e não há outros campos com a mesma política. Qual é a escolha mais apropriada?

Classificação

Uma property definida apenas com getter é classificada como:

Passo 8 de 8

Aplicação final: reutilizar e verificar o descritor

Integre um descritor reutilizável, execute verificações locais e justifique quando ele oferece vantagem sobre properties específicas.

Uma implementação reutilizável completa

Do protocolo ao uso em classes

O descritor guarda apenas sua configuração e os nomes associados. Cada valor concreto fica no __dict__ da instância consumidora, sob uma chave interna. Assim, a mesma classe TextoMinimo controla campos de Produto e Categoria sem misturar valores.

Configuração compartilhada, estado separado

Observe que cada declaração cria um objeto descritor próprio, enquanto cada objeto consumidor mantém seus próprios valores internos.

Diagrama mostrando descritores TextoMinimo associados aos campos nome e descricao de Produto e ao campo titulo de Categoria; instâncias diferentes possuem compartimentos de armazenamento interno separados.

O descritor contém regra e associação; as instâncias contêm os valores.

Script autocontido

Crie um arquivo, por exemplo verificar_descritor.py, cole o script inteiro e execute python verificar_descritor.py.

python
class TextoMinimo:
    def __init__(self, minimo):
        self.minimo = minimo

    def __set_name__(self, owner, name):
        self.nome_publico = name
        self.chave_interna = f"_{name}"

    def __get__(self, instance, owner):
        if instance is None:
            return self
        try:
            return instance.__dict__[self.chave_interna]
        except KeyError:
            raise AttributeError(
                f"{self.nome_publico!r} ainda não foi definido"
            ) from None

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError(f"{self.nome_publico} deve ser str")
        if len(value) < self.minimo:
            raise ValueError(
                f"{self.nome_publico} deve ter ao menos {self.minimo} caracteres"
            )
        instance.__dict__[self.chave_interna] = value

    def __delete__(self, instance):
        try:
            del instance.__dict__[self.chave_interna]
        except KeyError:
            raise AttributeError(
                f"{self.nome_publico!r} ainda não foi definido"
            ) from None


class Produto:
    nome = TextoMinimo(3)
    descricao = TextoMinimo(10)


class Categoria:
    titulo = TextoMinimo(4)


p1 = Produto()
p2 = Produto()
categoria = Categoria()

p1.nome = "Teclado"
p1.descricao = "Teclado sem fio"
p2.nome = "Mouse"
p2.descricao = "Mouse ergonomico"
categoria.titulo = "Casa"

# Cada instância e cada campo conservam valores independentes.
assert p1.nome == "Teclado"
assert p2.nome == "Mouse"
assert p1.descricao == "Teclado sem fio"
assert categoria.titulo == "Casa"

# Acesso pela classe devolve o objeto descritor escolhido como interface.
assert isinstance(Produto.nome, TextoMinimo)
assert Produto.nome.nome_publico == "nome"
assert Produto.descricao.chave_interna == "_descricao"

# Uma falha de validação não altera o valor que já era válido.
try:
    p1.nome = "ab"
except ValueError:
    pass
else:
    raise AssertionError("Era esperado ValueError")
assert p1.nome == "Teclado"

try:
    categoria.titulo = 42
except TypeError:
    pass
else:
    raise AssertionError("Era esperado TypeError")
assert categoria.titulo == "Casa"

# O descritor de dados vence uma chave pública conflitante na instância.
p1.__dict__["nome"] = "Valor falso"
assert p1.nome == "Teclado"

# Após del, a leitura segue o contrato de atributo indisponível.
del p2.nome
try:
    p2.nome
except AttributeError:
    pass
else:
    raise AssertionError("Era esperado AttributeError")

print("Todas as verificações passaram.")

Verifique e estenda localmente

Leitura dos resultados

Se a execução terminar com Todas as verificações passaram., o script comprovou: associação automática de nomes, isolamento entre instâncias e campos, validação antes da gravação, acesso pela classe, precedência do descritor de dados e indisponibilidade após remoção.

Agora acrescente, no corpo de Produto, a linha marca = TextoMinimo(3). Depois de criar p1, atribua p1.marca = "ABC" e verifique com assert p1.marca == "ABC". Tente também p1.marca = "X", capture ValueError e confirme que p1.marca ainda é "ABC".

Dica

O que a extensão demonstra

marca recebe outro objeto TextoMinimo, com sua própria associação e configuração. Você reutiliza a política sem copiar getters, setters e mensagens para cada campo.

Relate suas evidências

Quais resultados da execução comprovam isolamento, preservação do estado após erro e precedência? Por que o campo marca pode reutilizar a implementação sem compartilhar seu valor com nome?

Escreva pelo menos 120 caracteres (0/120).

Decisão de projeto e síntese

Resumo

Checklist do descritor reutilizável

Use o protocolo para concentrar uma política repetida de atributos, mantendo a responsabilidade pelo estado nos objetos que consomem essa política.

  • set_name transforma o nome público declarado em uma chave interna e em mensagens específicas do campo.
  • get, set e, quando adotado, delete definem os contratos de leitura, escrita e remoção.
  • O descritor é compartilhado como configuração; cada instância armazena seu valor em seu próprio dict.
  • Como TextoMinimo define set, ele é um descritor de dados e prevalece sobre uma chave pública homônima no dict da instância.
  • Prefira property para uma regra particular de um atributo; crie um descritor quando a mesma política trouxer ganho real em vários campos ou classes.

Escolha consciente

Em que situação você escolheria TextoMinimo em vez de uma property específica? Cite o ganho de reutilização e um custo da escolha.

Escreva pelo menos 90 caracteres (0/90).

Tutorial concluído

Parabéns! Você concluiu: Reutilizar controle de atributos com descritores

Muito bem! Você consegue concentrar regras de atributos em descritores, preservar valores por instância e prever a precedência na leitura.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso