[[ch_descriptors]]
== Descritores de Atributos
:example-number: 0
:figure-number: 0

[quote, Raymond Hettinger, guru de Python e um de seus desenvolvedores principais]
____
Aprender sobre descritores não apenas dá acesso a um conjunto maior de ferramentas, cria também uma maior compreensão sobre o funcionamento de Python e uma apreciação pela elegância de seu design.footnote:[Raymond Hettinger, https://docs.python.org/pt-br/3/howto/descriptor.html["HowTo - Guia de descritores"].]
____

Descritores((("descriptors")))((("attribute descriptors", "purpose of"))) são uma forma de reutilizar a mesma lógica de acesso em múltiplos atributos. Por exemplo, tipos de campos em ORMs ("Object Relational Mapping" - _Mapeamento Objeto-Relacional_), como o ORM do Django e o SQLAlchemy, são descritores, gerenciando o fluxo de dados dos campos em um registro de banco de dados para atributos de objetos de Python, e vice-versa.

Um((("&#x005F;&#x005F;get&#x005F;&#x005F;")))((("&#x005F;&#x005F;set&#x005F;&#x005F;")))((("&#x005F;&#x005F;delete&#x005F;&#x005F;"))) descritor é uma classe que implementa um protocolo dinâmico, composto pelos métodos `+__get__+`, `+__set__+`, e `+__delete__+`. A classe `property` implementa o protocolo descritor completo. Como habitual em protocolos dinâmicos, implementações parciais são aceitáveis. E, na verdade, a maioria dos descritores que vemos em código real implementam apenas
`+__get__+` e `+__set__+`, e muitos implementam apenas um destes métodos.

Descritores são um recurso característico de Python, presentes não apenas no nível das aplicações mas também na infraestrutura da linguagem.
Funções definidas pelo usuário são descritores. Veremos como o protocolo descritor permite que métodos operem como métodos vinculados ou desvinculados, dependendo de como são invocados.

Entender os descritores é crucial para dominar Python. Esse capítulo é sobre isso.

Nas próximas páginas((("attribute descriptors", "topics covered"))) vamos refatorar o exemplo da loja de comida orgânica a granel, visto na <<prop_validation_sec>>, substituindo propriedades por descritores.
Isso tornará mais fácil reutilizar a lógica de validação de atributos em diferentes classes.

Vamos estudar os conceitos de descritores dominantes e não dominantes, e entender que as funções de Python são descritores.
Para finalizar, veremos algumas dicas para a implementação de descritores.


[[whats_new_descriptor_sec]]
=== Novidades neste capítulo

O((("attribute descriptors", "significant changes to"))) exemplo do descritor `Quantity`, na <<auto_storage_sec>>, foi dramaticamente simplificado, graças ao método especial `+__set_name__+`, adicionado ao protocolo descritor no Python 3.6. Nessa mesma seção, removi o exemplo da fábrica de propriedades, pois ele se tornou irrelevante: o ponto ali era mostrar uma solução alternativa para o problema de `Quantity`, mas com `+__set_name__+` a solução com o descritor se tornou mais simples.

A classe `AutoStorage`, que aparecia na <<new_descr_type_sec>>, também foi removida, pois o mesmo `+__set_name__+` a tornou obsoleta.

[[validating_descriptor_sec]]
=== Exemplo de descritor: validação de atributos

Como((("attribute descriptors", "attribute validation", id="ADvalidation23")))((("attributes", "using attribute descriptors for validation", id="ATvalidation23"))) vimos na <<coding_prop_factory_sec>>, uma fábrica de propriedades é uma maneira de evitar código repetitivo de _getters_ e _setters_, aplicando padrões de programação funcional.
Um fábrica de propriedades é uma função de ordem superior que cria um conjunto de funções de acesso parametrizadas e constrói uma instância de propriedade customizada, com clausuras para manter configurações como `storage_name`.
A forma orientada a objetos de resolver o mesmo problema é uma classe descritora.

Vamos seguir com a série de exemplos `LineItem` de onde paramos, na <<coding_prop_factory_sec>>, refatorando a fábrica de propriedades `quantity` em uma classe descritora `Quantity`.
Isso vai torná-la mais fácil de usar.

==== LineItem versão #3: Um descritor simples

Como dito na introdução, uma classe que implemente um método `+__get__+`, um `+__set__+` ou um
`+__delete__+` é um descritor. Podemos usar um descritor declarando instâncias dele como atributos de classe em outra classe.


Vamos criar um descritor `Quantity`, e a classe `LineItem` vai usar duas instâncias de `Quantity`: uma para gerenciar o atributo `weight`, a outra para `price`. Um((("UML class diagrams", "managed and descriptor classes"))) diagrama ajuda: dê uma olhada na <<lineitem3_uml>>.

[[lineitem3_uml]]
.Diagrama de classe UML para `LineItem` usando uma classe descritora chamada `Quantity`. Atributos sublinhados no UML são atributos de classe. Observe que `weight` e `price` são instâncias de `Quantity` na classe `LineItem`, mas instâncias de `LineItem` também têm seus próprios atributos `weight` e `price`, onde esses valores são armazenados.
image::../images/flpy_2301.png[Diagrama de classes UML para `Quantity` e `LineItem`]

Note que a palavra `weight` aparece duas vezes na <<lineitem3_uml>>, pois na verdade há dois atributos diferentes chamados `weight`: um é um atributo de classe de `LineItem`, o outro é um atributo de instância que existirá em cada objeto `LineItem`. O mesmo se aplica a `price`.

===== Termos para entender descritores

Implementar((("attribute descriptors", "relevant terminology"))) e usar descritores envolve vários componentes, então é útil ser preciso ao nomeá-los.
Vou utilizar termos e definições abaixo nas descrições dos exemplos desse capítulo.
Será mais fácil entendê-los após ver o código, mas quis colocar todas as definições no início, para você poder voltar a elas quando necessário.

Classe descritora:: Uma((("descriptor classes"))) classe que implementa o protocolo descritor. Por exemplo, `Quantity` na <<lineitem3_uml>>.

Classe gerenciada:: A((("managed classes"))) classe onde as instâncias do descritor são declaradas, como atributos de classe. Na <<lineitem3_uml>>, `LineItem` é a classe gerenciada.

Instância do descritor:: Cada((("descriptor instances"))) instância de uma classe descritora, declarada como um atributo de classe da classe gerenciada. Na <<lineitem3_uml>>, cada instância do descritor está representada pela seta de composição com um nome sublinhado (na UML, o sublinhado indica um atributo de classe). Os diamantes pretos tocam a classe `LineItem`, que contém as instâncias do descritor.

Instância gerenciada:: Uma((("managed instances"))) instância da classe gerenciada. Nesse exemplo, instâncias de `LineItem` são as instâncias gerenciadas (elas não aparecem no diagrama de classe).

Atributo de armazenamento:: Um((("storage attributes"))) atributo da instância gerenciada que mantém o valor de um atributo gerenciado para aquela instância específica. Na <<lineitem3_uml>>, os atributos de instância `weight` e `price` de `LineItem` são atributos de armazenamento. Eles são diferentes das instâncias do descritor, que são sempre atributos de classe.

Atributos gerenciados:: Um((("managed attributes"))) atributo público na classe gerenciada que é controlado por uma instância do descritor, com os valores mantidos em atributos de armazenamento. Em outras palavras, uma instância do descritor e um atributo de armazenamento fornecem a infraestrutura para um atributo gerenciado.

É importante entender que instâncias de `Quantity` são atributos de classe de `LineItem`. Este((("UML class diagrams", "annotated with MGN"))) ponto fundamental é realçado pelas "engenhocas" (_mills_) e bugigangas (_gizmos_) na <<lineitem3_uml_mgn>>.

[[lineitem3_uml_mgn]]
.Diagrama de classe UML anotado com MGN (Mills & Gizmos Notation - Notação de Engenhocas e Bugigangas): classes são engenhocas que produzem bugigangas—as instâncias. A engenhoca `Quantity` produz duas bugigangas de cabeça redonda, que são anexadas à engenhoca `LineItem`: `weight` e `price`. A engenhoca `LineItem` produz bugigangas retangulares que tem seus próprios atributos `weight` e `price`, onde aqueles valores são armazenados.
image::../images/flpy_2302.png[Diagrama de classe UML+MGN para `Quantity` e `LineItem`]

[role="pagebreak-before less_space"]
[[mgn_box1]]
.Introduzindo a notação Engenhocas & Bugigangas (_Mills & Gizmos_)
****
Após((("Mills &amp; Gizmos Notation (MGN)"))) explicar descritores várias vezes, percebi que a UML não é muito boa para mostrar as relações entre classes e instâncias, tal como a relação entre uma classe gerenciada e as instâncias do descritor.footnote:[Classes e instâncias são representadas por retângulos em diagramas de classe UML. Há diferenças visuais, mas instâncias raramente aparecem em diagramas de classe, entao desenvolvedores podem não reconhecê-las como tal.] Daí inventei minha própria "linguagem", a Notação Engenhocas e Bugigangas (MGN), que uso para anotar diagramas UML.

A MGN é projetada para tornar bastante clara a diferença entre classes e instâncias.  Veja a <<mgn_diagram_demo>>. Na MGN, uma classe aparece como uma "engenhoca", uma máquina complexa que produz bugigangas. Classes/engenhocas são sempre máquina com alavancas e mostradores. As bugigangas são as instâncias, e elas têm uma aparência bem mais simples. Quando este livro é gerado em cores, as bugigangas têm a mesma cor da engenhoca que as produziu.

[[mgn_diagram_demo]]
.Esboço MGN mostrando a classe `LineItem` produzindo três instâncias, e `Quantity` produzindo duas. Uma instância de `Quantity` está recuperando um valor armazenado em uma instância de `LineItem`.
image::../images/flpy_2303.png[Esboço MGN para `LineItem` e `Quantity`]

Para este exemplo, desenhei instâncias de `LineItem` como linhas em uma fatura tabular, com três células representando os três atributos (`description`, `weight` e `price`). Como as instâncias de `Quantity` são descritores, eles têm uma lente de aumento para `+__get__+` (_obter_) os valores, e uma garra para `+__set__+` (_definir_) os valores. Quando chegarmos às metaclasses, você me agradecerá por esses desenhos.
****

Mas chega de rabiscos por enquanto. Aqui está o código: o <<quantity_v3>> mostra a classe descritora `Quantity`, e o <<lineitem_class_v3>> lista a nova classe `LineItem` usando duas instâncias de `Quantity`.

[[quantity_v3]]
.bulkfood_v3.py: o descritor `Quantity` não aceita valores negativos
====
[source, python]
----
include::../code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_QUANTITY_V3]
----
====
<1> O descritor é um recurso baseado em protocolo: não é necessário criar uma subclasse para implementá-lo.
<2> Cada instância de `Quantity` terá um atributo `storage_name`: é o nome do atributo de armazenamento que vai manter o valar nas instâncias gerenciadas.
<3> O `+__set__+` é chamado quando ocorre uma tentativa de atribuir um valor a um atributo gerenciado. Aqui, `self` é a instância do descritor (isto é, `LineItem.weight` ou `LineItem.price`), `instance` é a instância gerenciada (uma instância de `LineItem`) e `value` é o valor que está sendo atribuído.
<4> Precisamos armazenar o valor do atributo diretamente no `+__dict__+`; chamar pass:[<code>set&#x200b;attr&#x200b;(instance, self.storage_name)</code>] dispararia novamente o método `+__set__+`, levando a uma recursão infinita.
<5> Precisamos implementar `+__get__+`, pois o nome do atributo gerenciado pode não ser igual ao `storage_name`. O argumento `owner` será explicado a seguir.

Implementar `+__get__+` é necessário porque um usuário poderia escrever algo assim:

[source, python]
----
class House:
    rooms = Quantity('number_of_rooms')
----

Na classe `House`, o atributo gerenciado é `rooms`, mas o atributo de armazenamento é `number_of_rooms`.
Dada uma instância de `House` chamada `chaos_manor`, acessar e modificar `chaos_manor.rooms` passa pela instância do descritor `Quantity` ligada a `rooms`, mas acessar e modificar
`chaos_manor.number_of_rooms` escapa ao descritor.

Observe que `+__get__+` recebe três argumentos: `self`, `instance` e `owner`. O argumento `owner` é uma referência à classe gerenciada (por exemplo, `LineItem`), e é útil se você quiser que o descritor suporte o acesso a um atributo de classe—talvez para emular o comportamento default de Python, de procurar um atributo de classe quando o nome não é encontrado na instância.

Se um atributo gerenciado, tal como `weight`, é acessado através da classe como pass:[<code>Line&#x200b;Item.weight</code>], o método `+__get__+` do descritor recebe `None` como valor do argumento `instance`.

Para suportar introspecção e outras técnicas de metaprogramação pelo usuário, é uma boa prática fazer `+__get__+` devolver a instância do descritor quando o atributo gerenciado é acessado através da classe. Para fazer isso, escreveríamos `+__get__+` assim:

[source, python]
----
    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.storage_name]
----

O <<lineitem_class_v3>> demonstra o uso de `Quantity` em `LineItem`.

[[lineitem_class_v3]]
.bulkfood_v3.py: descritores `Quantity` gerenciam atributos em `LineItem`
====
[source, python]
----
include::../code/23-descriptor/bulkfood/bulkfood_v3.py[tags=LINEITEM_V3]
----
====
<1> A primeira instância do descritor vai gerenciar o atributo `weight`.
<2> A segunda instância do descritor vai gerenciar o atributo `price`.
<3> O restante do corpo da classe é tão simples e limpo como o código orginal em _bulkfood_v1.py_ (no <<lineitem_class_v1>>).

O código no <<lineitem_class_v3>> funciona como esperado, evitando a venda de trufas por $0:footnote:[O quilo de trufas brancas custa milhares de reais. Impedir a venda de trufas por $0,01 fica como exercício para a leitora com espírito de aventura. Conheço um caso real, de uma pessoa que comprou uma enciclopédia de estatísticas de 1.800 dólares por 18 dólares, devido a um erro em uma loja online(neste caso não foi na _Amazon.com_).]

[source, python]
----
>>> truffle = LineItem('White truffle', 100, 0)
Traceback (most recent call last):
    ...
ValueError: value must be > 0
----

[WARNING]
====
Ao programar os métodos `+__get__+` e `+__set__+` de um descritor, tenha em mente o significado dos argumentos `self` e `instance`: `self` é a instância do descritor, `instance` é a instância gerenciada. Descritores que gerenciam atributos de instância devem armazenar os valores nas instâncias gerenciadas. É por isso que Python fornece o argumento `instance` aos métodos do descritor.
====

Pode ser tentador, mas é um erro, armazenar o valor de cada atributo gerenciado na própria instância do descritor. Em outras palavras, em vez de escrever o método `+__set__+` assim:

[source, python]
----
    instance.__dict__[self.storage_name] = value
----

escrever a alternativa tentadora mas ruim, assim:

[source, python]
----
    self.__dict__[self.storage_name] = value
----

Para entender porque isso está errado, pense no significado dos dois primeiros argumentos passados a `+__set__+`: `self` e `instance`. Aqui, `self` é a instância do descritor, que na verdade é um atributo de classe da classe gerenciada. Você pode ter milhares de instâncias de `LineItem` na memória em um dado momento, mas terá apenas duas instâncias dos descritores: os atributos de classe `LineItem.weight` e `LineItem.price`. Então, qualquer coisa armazenada nas próprias instâncias do descritor é na verdade parte de um atributo de classe de `LineItem`, e portanto é compartilhada por todas as instâncias de `LineItem`.

Um inconveniente do <<lineitem_class_v3>> é a necessidade de repetir os nomes dos atributos quando os descritores são instanciados no corpo da classe gerenciada. Seria bom se a classe `LineItem` pudesse ser declarada assim:

[source, python]
----
class LineItem:
    weight = Quantity()
    price = Quantity()

    # o restante dos métodos permanece igual
----

Da forma como está escrito, o <<lineitem_class_v3>> exige nomear explicitamente cada `Quantity`, algo não apenas inconveniente, mas também perigoso. Se um programador, ao copiar e colar código, se esquecer de editar os dois nomes, e terminar com uma linha como `price = Quantity('weight')`, o programa vai se comportar de forma muito errática, sobrescrevendo o valor de `weight` sempre que `price` for definido.

O problema é que—como vimos no <<ch_refs_mut_mem>>—o lado direito de uma atribuição é executado antes da variável existir. A expressão `Quantity()` é avaliada para criar uma instância do descritor, e não há como o código na classe `Quantity` adivinhar o nome da variável à qual o descritor será vinculado (por exemplo, `weight` ou `price`).

Felizmente, o protocolo descritor agora suporta o muito bem batizado método `+__set_name__+`. Veremos a seguir como usá-lo.

[NOTE]
====
Nomear automaticamente o atributo de armazenamendo de um descritor contumava ser uma tarefa espinhosa. Na primeira edição do _Python Fluente_, dediquei várias páginas e muitas linhas de código neste capítulo e no seguinte para apresentar diferentes soluções, incluindo o uso de um decorador de classe e depois metaclasses (no <<ch_class_metaprog>>).
Tudo isso ficou mais simples no Python 3.6.
====

[[auto_storage_sec]]
==== LineItem versão #4: Nomeando atributos de armazenamento automaticamente

Para evitar a redigitação do nome do atributo em instâncias do descritor, vamos implementar
`+__set_name__+`, para definir o `storage_name` de cada instância de `Quantity`. O método especial
`+__set_name__+` foi acrescentado ao protocolo descritor no Python 3.6.

O interpretador invoca `+__set_name__+` em cada descritor encontrado no corpo de uma `class`—se o descritor implementar esse método.footnote:[Mais precisamente, `+__set_name__+` é invocado por `+type.__new__+`—o construtor de objetos que representam classes. A classe embutida `type` é na verdade uma metaclasse, a classe default de classes definidas pelo usuário. Isso é um pouco difícil de entender de início, mas fique tranquila: o <<ch_class_metaprog>> é dedicado à configuração dinâmica de classes, incluindo o conceito de metaclasses.]

No <<lineitem_class_v4>>, a classe descritora `Quantity` não precisa de um `+__init__+`.
Em vez disso, `+__set_item__+` armazena o nome do atributo de armazenamento.

[[lineitem_class_v4]]
.bulkfood_v4.py: `+__set_name__+` define o nome para cada instância do descritor `Quantity`
====
[source, python]
----
include::../code/23-descriptor/bulkfood/bulkfood_v4.py[tags=LINEITEM_V4]
----
====
<1> `self` é a instância do descritor (não a instância gerenciada), `owner` é a classe gerenciada e `name` é o nome do atributo de `owner` ao qual essa instância do descritor foi atrbuída no corpo da classe de `owner`.
<2> Isso é o que o `+__init__+` fazia no <<quantity_v3>>.
<3> O método `+__set__+` aqui é exatamente igual ao do <<quantity_v3>>.
<4> Não é necessário implementar `+__get__+`, porque o nome do atributo de armazenamento é igual ao nome do atributo gerenciado. A expressão `product.price` obtém o atributo `price` diretamente da instância de `LineItem`.
<5> Não é necessário passar o nome do atributo gerenciado para o construtor de `Quantity`. Esse era o objetivo dessa versão.

Olhando para o <<lineitem_class_v4>>, pode parecer muito código apenas para gerenciar um par de atributos, mas é importante perceber que a lógica do descritor foi agora abstraida em uma unidade de código diferente: a classe `Quantity`.
Normalmente não definimos um descritor no mesmo módulo em que ele é usado,
mas em um módulo utilitário separado,
projetado para ser usado por toda a aplicação—ou mesmo por muitas aplicações,
se estivermos desenvolvendo uma bliblioteca ou um framework.

Tendo isso em mente, o <<lineitem_class_v4c>> representa melhor o uso típico de um descritor.

[[lineitem_class_v4c]]
.bulkfood_v4c.py: uma definição mais limpa de `LineItem`; a classe descritora `Quantity` agora reside no módulo importado `model_v4c`
====
[source, python]
----
include::../code/23-descriptor/bulkfood/bulkfood_v4c.py[tags=LINEITEM_V4C]
----
====
<1> Importa o módulo `model_v4c`, onde `Quantity` é implementada.
<2> Coloca `model.Quantity` em uso.

Usuários do Django vão perceber que o <<lineitem_class_v4c>> se parece muito com uma definição de modelo. Isso não é uma coincidência: os campos de modelos Django são descritores.

Já que descritores são implementado como classes, podemos aproveitar a herança para reutilizar parte do código que já temos em novos descritores. É o que faremos na próxima seção.

[[new_descr_type_sec]]
==== LineItem versão #5: um novo tipo descritor

A loja imaginária de comida orgânica encontra um obstáculo: de alguma forma, uma instância de um produto foi criada com uma descrição vazia, e o pedido não pode ser processado. Para prevenir isso, criaremos um novo descritor: `NonBlank`. Ao projetar `NonBlank`, percebemos que ele será muito parecido com o descritor `Quantity`, exceto pela lógica de validação.

Isso leva a uma refatoração, resultando em `Validated`, uma classe abstrata que sobrescreve um método
`+__set__+`, invocando o método `validate`, que precisa ser implementado por subclasses.

Vamos então reescrever `Quantity` e implementar `NonBlank`, herdando de `Validated` e programando apenas os métodos `validate`.

A relação entre `Validated`, `Quantity` e `NonBlank` é uma aplicação do _método modelo_ ("template method"), como descrito no clássico _Design Patterns_:

[quote]
____
Um método modelo define um algoritimo em termos de operações abstratas que subclasses sobrescrevem para fornecer o comportamento concreto.footnote:[Gamma et al., _Design Patterns: Elements of Reusable Object-Oriented Software_, p. 326. (_Padrões de Projetos: Soluções Reutilizáveis de Software Orientados a Objetos_)]
____

No <<model_v5_abc>>, `+Validated.__set__+` é um método modelo e `self.validate` é a operação abstrata.

[[model_v5_abc]]
.model_v5.py: the `Validated` ABC
====
[source, python]
----
include::../code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_ABC]
----
====
<1> `+__set__+` delega a validação para o método `validate`...
<2> ...e então usa o `value` devolvido para atualizar o valor armazenado.
<3> `validate` é um método abstrato; este é o método modelo.

Alex Martelli prefere chamar este padrão de projeto _Auto-Delegação_ ("_Self-Delegation_"),
e concordo que é um nome mais descritivo: a primeira linha de `+__set__+` auto-delega para
`validate`.footnote:[Slide #50 da palestra https://fpy.li/23-1["Python Design Patterns" (_Padrões de Projeto de Python_)] (EN), de Alex Martelli. Altamente recomendada.]

As subclasses concretas de `Validated` neste exemplo são `Quantity` e `NonBlank`, apresentadas no <<model_v5_sub>>.

[[model_v5_sub]]
.model_v5.py: `Quantity` e `NonBlank`, subclasses concretas de `Validated`
====
[source, python]
----
include::../code/23-descriptor/bulkfood/model_v5.py[tags=MODEL_V5_VALIDATED_SUB]
----
====
<1> Implementação do método modelo exigida pelo método abstrado `Validated.validate`.
<2> Se não sobrar nada após a remoção os espaços em branco antes e depois do valor, este é rejeitado.
<3> Exigir que os métodos `validate` concretos devolvam o valor validado dá a eles a oportunidade de limpar, converter ou normalizar os dados recebidos. Neste caso, `value` é devolvido sem espaços iniciais ou finais.

Usuários de _model_v5.py_ não precisam saber todos esses detalhes. O que importa é poder usar `Quantity` e `NonBlank` para automatizar a validação de atributos de instância. Veja a última classe `LineItem` no <<lineitem_class_v5>>.

[[lineitem_class_v5]]
.bulkfood_v5.py: `LineItem` usando os descritores `Quantity` e `NonBlank`
====
[source, python]
----
include::../code/23-descriptor/bulkfood/bulkfood_v5.py[tags=LINEITEM_V5]
----
====
<1> Importa o módulo `model_v5`, dando a ele um nome amigável.
<2> Usa `model.NonBlank`. O restante do código não foi modificado.

Os exemplos de `LineItem` que vimos neste capítulo demonstram um uso típico de descritores, para gerenciar atributos de dados.
Descritores como `Quantity` são chamado descritores dominantes, pois seu método `+__set__+` sobrescreve (isto é, intercepta e anula) a definição de um atributo de instância com o mesmo nome na instância gerenciada. Entretanto, há também descritores não dominantes. Vamos explorar essa diferença detalhadamente na próxima seção.((("", startref="ADvalidation23")))((("", startref="ATvalidation23")))


=== Descritores dominantes versus descritores não dominantes

Recordando((("attribute descriptors", "overriding versus nonoverriding", id="ADovervnon23")))((("nonoverriding descriptors", id="nonover23")))((("overriding descriptors", id="overrid23"))), há uma importante assimetria na forma como Python lida com atributos. Ler um atributo através de uma instância normalmente devolve o atributo definido na instância. Mas se tal atributo não existir na instância, um atributo de classe será obtido. Por outro lado, uma atribuição a um atributo em uma instância normalmente cria o atributo na instância, sem afetar a classe de forma alguma.

Essa assimetria também afeta descritores, criando efetivamente duas grandes categorias de descritores, dependendo do método `+__set__+` estar ou não implementado.
Se `+__set__+` estiver presente, a classe é um descritor dominante; caso contrário, ela é um descritor não dominante.
Esses termos farão sentido quando examinarmos os comportamentos de descritores, nos próximos exemplos.

Observar as categorias diferentes de descritores exige algumas classes, então vamos usar o código no <<descriptorkinds_ex>> como nossa bancada de testes para as próximas seções.

[TIP]
====
Todos os métodos `+__get__+` e `+__set__+` no <<descriptorkinds_ex>> chamam `print_args`. Assim, suas invocações são apresentadas de uma forma legível. Entender `print_args` e suas funções auxiliares, `cls_name` e `display`, não é importante, então não se deixe distrair por elas.
====

[[descriptorkinds_ex]]
.descriptorkinds.py: classes simples para estudar os comportamentos dominantes de descritores
====
[source, python]
----
include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS]
----
====
<1> Uma classe descritora dominante com `+__get__+` e `+__set__+`.
<2> A função `print_args` é chamada por todos os métodos do descritor neste exemplo.
<3> Um descritor dominante sem um método `+__get__+`.
<4> Nenhum método `+__set__+` aqui, estão este é um descritor não dominante.
<5> A classe gerenciada, usando uma instância de cada uma das classes descritoras.
<6> O método `spam` está aqui para efeito de comparação, pois métodos também são descritores.

Nas próximas seções, examinaremos o comportamento de leitura e escrita de atributos na classe `Managed` e em uma de suas instâncias, passando por cada um dos diferentes descritores definidos.

[[overriding_descriptor_sec]]
==== Descritores dominantes

Um descritor que implementa o método `+__set__+` é um _descritor dominante_ pois, apesar de ser um atributo de classe, um descritor que implementa `+__set__+` irá  sobrescrever tentativas de atribuição a atributos de instância. É assim que o <<lineitem_class_v4>> foi implementado. Propriedades também são descritores dominantes: se você não fornecer uma função _setter_, o `+__set__+` default da classe `property` vai gerar um `AttributeError`, para sinalizar que o atributo é somente para leitura.

[WARNING]
====
Contribuidores e autores da comunidade Python usam termos diferentes ao discutir esses conceitos. Adotei "descritor dominante" (_overriding descriptor_), do livro _Python in a Nutshell_.
A documentação oficial de Python usa "descritor de dados" (_data descriptor_) mas "descritor dominante" destaca o comportamento especial.
Descritores dominantes também são chamados "descritores forçados" (_enforced descriptors_).
Sinônimos para descritores não dominantes incluem "descritores sem dados" (_nondata descriptors_, na documentação oficial em português) ou "descritores ocultáveis" (_shadowable descriptors_).
====

Dado o código no <<descriptorkinds_ex>>, alguns experimentos com um descritor dominante podem ser vistos no <<descriptorkinds_demo1>>.

[[descriptorkinds_demo1]]
.O comportamento de um descritor dominante
====
[source, python]
----
include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO1]
----
====
<1> Cria o objeto `Managed`, para testes.
<2> `obj.over` aciona o método `+__get__+` do descritor, passando a instância gerenciada `obj` como segundo argumento.
<3> `Managed.over` aciona o método `+__get__+` do descritor, passando `None` como segundo argumento (`instance`).
<4> Atribuir a `obj.over` aciona o método `+__set__+` do descritor, passando o valor `7` como último argumento.
<5> Ler `obj.over` ainda invoca o método `+__get__+` do descritor.
<6> Contorna o descritor, definindo um valor diretamente no `+obj.__dict__+`.
<7> Verifica se aquele valor está no `+obj.__dict__+`, sob a chave `over`.
<8> Entretanto, mesmo com um atributo de instância chamado `over`, o descritor `Managed.over` continua interceptando tentativas de ler `obj.over`.

==== Descritor dominante sem &#x5f;&#x5f;get&#x5f;&#x5f;

Propriedades e outros descritores dominantes, tal como os campos de modelo do Django, implementam tanto `+__set__+` quanto `+__get__+`. Mas também é possível implementar apenas `+__set__+`, como vimos no <<lineitem_class_v3>>. Neste caso, apenas a escrita é controlada pelo descritor. Ler o descritor através de uma instância irá devolver o próprio objeto descritor, pois não há um
`+__get__+` para tratar daquele acesso. Se um atributo de instância de mesmo nome for criado com um novo valor, através de acesso direto ao `+__dict__+` da instância, o método `+__set__+` continuará interceptando tentativas posteriores de definir aquele atributo, mas a leitura do atributo vai simplesmente devolver o novo valor na instância, em vez de devolver o objeto descritor. Em outras palavras, o atributo de instância vai ocultar o descritor, mas apenas para leitura. Veja o <<descriptorkinds_demo2>>.

[[descriptorkinds_demo2]]
.Descritor dominante sem `+__get__+`
====
[source, python]
----
include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO2]
----
====
<1> Este descritor dominante não tem um método `+__get__+`, então ler `obj.over_no_get` obtém a instância do descritor a partir da classe.
<2> A mesma coisa acontece se obtivermos a instância do descritor diretamente da classe gerenciada.
<3> Tentar definir um valor para `obj.over_no_get` invoca o método `+__set__+` do descritor.
<4> Como nosso `+__set__+` não faz modificações, ler `obj.over_no_get` novamente obtém a instância do descritor na classe gerenciada.
<5> Percorrendo o `+__dict__+` da instância para definir um atributo de instância chamado `over_no_get`.
<6> Agora aquele atributo de instância `over_no_get` oculta o descritor, mas apenas para leitura.
<7> Tentar atribuir um valor a `obj.over_no_get` continua passando pelo _set_ do descritor.
<8> Mas, para leitura, aquele descritor é ocultado enquanto existir um atributo de instância de mesmo nome.


==== Descritor não dominante

Um descritor que não implementa `+__set__+` é um descritor não dominante. Definir um atributo de instância com o mesmo nome vai ocultar o descritor, tornando-o incapaz de tratar aquele atributo naquela instância específica. Métodos e a `@functools.cached_property` são implementados como descritores não dominantes. O <<descriptorkinds_demo3>> mostra a operação de um descritor não dominante.

[[descriptorkinds_demo3]]
.Comportamento de um descritor não dominante
====
[source, python]
----
include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO3]
----
====
<1> `obj.non_over` aciona o método `+__get__+` do descritor, passando `obj` como segundo argumento.
<2> `Managed.non_over` é um descritor não dominante, então não há um `+__set__+` para interferir com essa atribuição.
<3> O `obj` agora tem um atributo de instância chamado `non_over`, que oculta o atributo do descritor de mesmo nome na classe `Managed`.
<4> O descritor `Managed.non_over` ainda está lá, e intercepta esse acesso através da classe.
<5> Se o atributo de instância `non_over` for excluído...
<6> ...então ler `obj.non_over` encontra o método `+__get__+` do descritor; mas observe que o segundo argumento é a instância gerenciada.

Nos exemplos anteriores, vimos várias atribuições a um atributo de instância com nome igual ao do descritor, com resultados diferentes dependendo da presença ou não de um método `+__set__+` no descritor.

A definição de atributos na classe não pode ser controlada por descritores ligados à mesma classe.
Em especial, isso significa que os próprios atributos do descritor podem ser danificados por atribuições à classe, como explicado na próxima seção.


==== Sobrescrevendo um descritor em uma classe

Independente do descritor ser ou não dominante, ele pode ser sobrescrito por uma atribuição à classe. Isso é uma((("monkey-patching"))) técnica de _monkey-patching_ mas, no <<descriptorkinds_demo4>>, os descritores são substituídos por números inteiros, algo que certamente quebraria a lógica de qualquer classe que dependesse dos descritores para seu funcionamento correto.

[[descriptorkinds_demo4]]
.Qualquer descritor pode ser sobrescrito na própria classe
====
[source, python]
----
include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO4]
----
====
<1> Cria uma nova instância para testes posteriores.
<2> Sobrescreve os atributos dos descritores na classe.
<3> Os descritores realmente desapareceram.

O <<descriptorkinds_demo4>> expõe outra assimetria entre a leitura e a escrita de atributos: apesar da leitura de um atributo de classe poder ser controlada por um `+__get__+` de um descritor ligado à classe gerenciada, a escrita em um atributo de classe não pode ser tratado por um `+__set__+` de um descritor ligado à mesma classe.

[TIP]
====
Para controlar a escrita a atributos em uma classe, é preciso associar descritores à classe da classe—em outras palavras, à metaclasse. Por default, a metaclasse de classes definidas pelo usuário é `type`, e não podemos acrescentar atributos a `type`. Mas, no <<ch_class_metaprog>>, vamos criar nossas próprias metaclasses.
====

Vamos ver agora como descritores são usados para implementar métodos no Python.((("", startref="ADovervnon23")))((("", startref="nonover23")))((("", startref="overrid23")))

[[methods_are_descriptors_sec]]
=== Métodos são descritores

Uma((("attribute descriptors", "methods as descriptors", id="ADmethodsas23"))) função dentro de uma classe se torna um método vinculado quando invocada em uma instância, porque todas as funções definidas pelo usuário possuem um método `+__get__+`, e portanto operam como descritores quando associados a uma classe.
O <<descriptorkinds_demo5>> demonstra a leitura do método `spam`, da classe `Managed`, apresentada no <<descriptorkinds_ex>>.


[[descriptorkinds_demo5]]
.Um método é um descritor não dominante
====
[source, python]
----
include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_DEMO5]
----
====
<1> Ler de `obj.spam` obtém um objeto método vinculado.
<2> Mas ler de `Managed.spam` obtém uma função.
<3> Atribuir um valor a `obj.spam` oculta o atributo de classe, tornando o método `spam` inacessível a partir da instância `obj`.

Funções não implementam `+__set__+`, portanto são descritores não dominantes, como mostra a última linha do <<descriptorkinds_demo5>>.

A outra lição fundamental do <<descriptorkinds_demo5>> é que `obj.spam` e `Managed.spam` devolvem objetos diferentes. Como de hábito com descritores, o `+__get__+` de uma função devolve uma referência para a própria função quando o acesso ocorre através da classe gerenciada. Mas quando o acesso vem através da instância, o `+__get__+` da função devolve um objeto método vinculado: um invocável que envolve a função e vincula a instância gerenciada (no exemplo, `obj`) ao primeiro argumento da função (isto é, `self`), como faz a função `functools.partial` (que vimos na <<functools_partial_sec>>).
Para um entendimento mais profundo desse mecanismo, dê uma olhada no <<func_descriptor_ex>>.

[[func_descriptor_ex]]
.method_is_descriptor.py: uma classe `Text`, derivada de `UserString`
====
[source, python]
----
include::../code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_EX]
----
====

Vamos então investigar o método `Text.reverse`. Veja o <<func_descriptor_demo>>.

[[func_descriptor_demo]]
.Experimentos com um método
====
[source, python]
----
include::../code/23-descriptor/method_is_descriptor.py[tags=FUNC_DESCRIPTOR_DEMO]
----
====
<1> O `repr` de uma instância de `Text` se parece com uma chamada ao construtor de `Text` que criaria uma instância idêntica.
<2> O método `reverse` devolve o texto escrito de trás para frente.
<3> Um método invocado na classe funciona como uma função.
<4> Observe os tipos diferentes: uma `function` e um `method`.
<5> `Text.reverse` opera como uma função, mesmo ao trabalhar com objetos que não são instâncias de `Text`.
<6> Toda função é um descritor não dominante. Invocar seu `+__get__+` com uma instância obtém um método vinculado a aquela instância.
<7> Invocar o `+__get__+` da função com `None` como argumento `instance` obtém a própria função.
<8> A expressão `word.reverse` na verdade invoca `+Text.reverse.__get__(word)+`, devolvendo o método vinculado.
<9> O objeto método vinculado tem um atributo `+__self__+`, contendo uma referência à instância na qual o método foi invocado.
<10> O atributo `+__func__+` do método vinculado é uma referência à função original, ligada à classe gerenciada.

O objeto método vinculado contém um método `+__call__+`, que trata a invocação em si. Este método chama a função original, referenciada em `+__func__+`, passando o atributo `+__self__+` do método como primeiro argumento. É assim que funciona a vinculação implícita do argumento `self` convencional.

O modo como funções são transformadas em métodos vinculados é um exemplo perfeito de como descritores são usados como infraestrutura da linguagem.

Após este mergulho profundo no funcionamento de descritores e métodos, vamos repassar alguns conselhos práticos sobre seu uso.((("", startref="ADmethodsas23")))

[[descriptor_usage_sec]]
=== Dicas para o uso de descritores

A((("attribute descriptors", "descriptor usage tips", id="ADtips23"))) lista a seguir trata de algumas consequências práticas das características dos descritores descritas acima:

Use `property` para manter as coisas simples:: A classe embutida `property` cria descritores dominantes, implementando `+__set__+` e `+__get__+`, mesmo se um método _setter_ não for definido.footnote:[Um método `+__delete__+` também é fornecido pelo decorador `property`, mesmo se você não definir um método _deleter_ (de exclusão).]
O `+__set__+` default de uma propriedade gera um `AttributeError: can't set attribute` (_AttributeError: não é permitido definir o atributo_), então uma propriedade é a forma mais fácil de criar um atributo somente para leitura, evitando o problema descrito a seguir.

Descritores somente para leitura exigem um `+__set__+`:: Se você usar uma classe descritora para implementar um atributo somente para leitura, precisa lembrar de programar tanto `+__get__+` quanto
`+__set__+`. Caso contrário, definir um atributo com o mesmo nome em uma instância vai ocultar o descritor. O método `+__set__+` de um atributo somente para leitura deve apenas gerar um `AttributeError` com uma mensagem adequada.footnote:[Python não é consistente nessas mensagens. Tentar modificar o atributo `c.real` de um número `complex` resulta em um `AttributeError: readonly attribute` (_AttributeError: atributo somente para leitura_), mas uma tentativa de mudar
`c.conjugate` (um método de `complex`) gera um `AttributeError: 'complex' object attribute 'conjugate' is read-only` (_AttributeError: o atributo 'conjugate' do objeto 'complex' é somente para leitura_). Até "read-only" está escrito de maneira diferente (na mensagem original em inglês).]

Descritores de validação podem funcionar apenas com `+__set__+`:: Em um descritor projetado apenas para validação, o método `+__set__+` deve verificar o argumento `value` recebido e, se ele for válido, atualizar o `+__dict__+` da instância diretamente, usando o nome da instância do descritor como chave. Dessa forma, ler o atributo de mesmo nome a partir da instância será tão rápido quanto possível, pois não vai precisar de um `+__get__+`. Veja o código no <<lineitem_class_v4>>.

_Caching_ pode ser feito de forma eficiente apenas com `+__get__+`:: Se você escrever apenas o método `+__get__+`, cria um descritor não dominante.
Eles são úteis para executar alguma computação custosa e então armazenar o resultado, definindo um atributo com o mesmo nome na instânciafootnote:[Entretanto, lembre-se que criar atributos de instância após o método `+__init__+` frustra a otimização de memória através de compartilhamento de chaves, como discutido na <<consequences_dict_internals>>.].
O atributo de mesmo nome na instância vai ocultar o descritor, daí acessos subsequentes a aquele atributo vão buscá-lo diretamente no `+__dict__+` da instância, sem acionar mais o `+__get__+` do descritor.
O decorador `@functools.cached_property` na verdade produz um descritor não dominante.

Métodos não especiais pode ser ocultados por atributos de instância:: Como funções e métodos implementam apenas `+__get__+`, eles são descritores não dominantes. Uma atribuição simples, como `my_obj.the_method = 7`, significa que acessos posteriores a `the_method` através daquela instância irão obter o número ++7++—sem afetar a classe ou outras instâncias. Essa questão, entretanto, não interfere com os métodos especiais. O interpretador só procura métodos especiais na própria classe. Em outras palavras, `repr(x)` é executado como `+x.__class__.__repr__(x)+`, então um atributo
`+__repr__+`, definido em `x`, não tem qualquer efeito em `repr(x)`. Pela mesma razão, a existência de um atributo chamado `+__getattr__+` em uma instância não vai subverter o algoritmo normal de acesso a atributos.

O fato de métodos não especiais poderem ser sobrepostos tão facilmente pode soar frágil e propenso a erros. Mas eu, pessoalmente, em mais de 20 anos programando em Python, nunca tive problemas com isso. Por outro lado, se você estiver criando muitos atributos dinâmicos, onde os nomes dos atributos vêm de dados que você não controla (como fizemos na parte inicial desse capítulo), então você precisa estar atenta para isso, e talvez implementar alguma filtragem ou reescrita (_escaping_) dos nomes dos atributos dinâmicos, para preservar sua sanidade.

[role="man-height"]
[NOTE]
====
A classe `FrozenJSON` no <<ex_explore1>> está a salvo de atributos de instância ocultando métodos, pois seus únicos métodos são métodos especiais e o método de classe `build`. Métodos de classe são seguros desde que sejam sempre acessados através da classe, como fiz com `FrozenJSON.build` no <<ex_explore1>>—mais tarde substituído por `+__new__+` no <<ex_explore2>>. As classes `Record` e `Event`, apresentadas na <<computed_props_sec>>, também estão a salvo: elas implementam apenas métodos especiais, métodos estáticos e propriedades. Propriedades são descritores dominantes, então não são ocultados por atributos de instância.
====

Para encerrar esse capítulo, vamos falar de dois recursos que vimos com as propriedades, mas não no contexto dos descritores: documentação e o tratamento de tentativas de excluir um atributo gerenciado.((("", startref="ADtips23")))

[[descriptor_doc_del_sec]]
=== Docstrings de descritores e a sobreposição de exclusão

A((("attribute descriptors", "descriptor docstring and overriding deletion"))) docstring de uma classe descritora é usada para documentar todas as instâncias do descritor na classe gerenciada.
O <<descriptor_help_screens>> mostra as telas de ajuda para a classe `LineItem` com os descritores `Quantity` e `NonBlank`, do
<<model_v5_sub>> e do <<lineitem_class_v5>>.

Isso é um tanto insatisfatório. No caso de `LineItem`, seria bom acrescentar, por exemplo, a informação de que `weight` deve ser expresso em quilogramas. Isso seria trivial com propriedades, pois cada propriedade controla um atributo gerenciado específico. Mas com descritores, a mesma classe descritora `Quantity` é usada para `weight` e `price`.footnote:[customizar o texto de ajuda para cada instância do descritor é supreendentemente difícil. Uma solução exige criar dinamicamente uma classe invólucro (_wrapper_) para cada instância do descritor.]

O segundo detalhe que discutimos com propriedades, mas não com descritores, é o tratamento de tentativas de apagar um atributo gerenciado.
Isso pode ser feito pela implementação de um método `+__delete__+` juntamente com (ou em vez de) os habituais `+__get__+` e/ou `+__set__+` na classe descritora.
Omiti deliberadamente falar de `+__delete__+`, porque acredito que seu uso no mundo real é raro.
Se você precisar disso, por favor consulte a seção https://fpy.li/23-2["Implementando descritores"] na documentação do https://fpy.li/dtmodel[Modelo de dados de Python].
Escrever um classe descritora boba com `+__delete__+` fica como exercício para a leitora ociosa.

[[descriptor_help_screens]]
.Capturas de tela do console de Python após os comandos `help(LineItem.weight)` e `help(LineItem)`.
image::../images/flpy_2304.png[Capturas de tela do console de Python com a ajuda de descritores.]

=== Resumo do capítulo

O((("attribute descriptors", "overview of"))) primeiro exemplo deste capítulo foi uma continuação dos exemplos `LineItem` do <<ch_dynamic_attrs>>. No <<lineitem_class_v3>>, substituímos propriedades por descritores. Vimos que um descritor é uma classe que fornece instâncias, que são instaladas como atributos na classe gerenciada. Discutir esse mecanismo exigiu uma terminologia especial, apresentando termos como _instância gerenciada_ e _atributo de armazenamento_.

Na <<auto_storage_sec>>, removemos a exigência de descritores `Quantity` serem declarados com um `storage_name` explícito, um requisito redundante e propenso a erros. A solução foi implementar o método especial `+__set_name__+` em `Quantity`, para armazenar o nome da propriedade gerenciada como `self.storage_name`.

A <<new_descr_type_sec>> mostrou como criar uma subclasse de uma classe descritora abstrata, para compartilhar código ao programar descritores especializados com alguma funcionalidade em comum.

[role="pagebreak-before less_space"]
Examinamos então os comportamentos diferentes de descritores, fornecendo ou omitindo o método
`+__set__+`, criando uma distinção fundamental entre descritores dominantes e não dominantes, também conhecidos como descritores de dados e sem dados. Por meio de testes detalhados, revelamos quando os descritores estão no controle, e quando são ocultados, contornados ou sobrescritos.

Em seguida, estudamos uma categoria específica de descritores não dominantes: métodos. Experimentos no console revelaram como uma função associada ao uma classe se torna um método ao ser acessada através de uma instância, se valendo do protocolo descritor.

Para concluir o capítulo, a <<descriptor_usage_sec>> trouxe dicas práticas, e a <<descriptor_doc_del_sec>> forneceu um rápido olhar sobre como documentar descritores.

[NOTE]
====
Como observado na <<whats_new_descriptor_sec>>, vários exemplos deste capítulo se tornaram mais simples graças ao método especial `+__set_name__+` do protocolo descritor, adicionado no Python 3.6. Isso é evolução da linguagem!
====

=== Para saber mais

Além((("attribute descriptors", "further reading on"))) da referência obrigatória ao capítulo https://docs.python.org/pt-br/3/reference/datamodel.html["Modelo de dados"], o https://docs.python.org/pt-br/3/howto/descriptor.html["HowTo - Guia de descritores"], de  Raymond Hettinger, é um recurso valioso--e parte da https://docs.python.org/pt-br/3/howto/[coleção de HOWTOS] na documentação oficial de Python.

Como sempre, em se tratando de assuntos relativos ao modelo de objetos de Python, o _Python in a Nutshell_, 3ª ed. (O'Reilly), de Martelli, Ravenscroft, e Holden é competente e objetivo. Martelli também tem uma apresentação chamada "Python's Object Model" (_O Modelo de Objetos de Python_), tratando com profundidade de propriedades e descritores (veja os https://fpy.li/23-5[slides] (EN) e o https://fpy.li/23-6[video] (EN)).

[WARNING]
====
Cuidado, qualquer tratamento de descritores escrito ou gravado antes da PEP 487 ser adotada, em 2016, corre o risco de conter exemplos desnecessariamente complicados hoje, pois `+__set_name__+` não era suportado nas versões de Python anteriores a 3.6.
====

Para mais exemplos práticos, o _Python Cookbook_, 3ª ed., de David Beazley e Brian K. Jones (O’Reilly), traz muitas receitas ilustrando descritores, dentre as quais quero destacar "6.12. Reading Nested and Variable-Sized Binary Structures" (_Lendo Estruturas Binárias Aninhadas e de Tamanho Variável_), "8.10. Using Lazily Computed Properties" (_Usando Propriedades Computadas de Forma Preguiçosa_), "8.13. Implementing a Data Model or Type System" (_Implementando um Modelo de Dados ou um Sistema de Tipos_) e "9.9. Defining Decorators As Classes" (_Definindo Decoradores como Classes_). Essa última receita trata das questões profundas envolvidas na interação entre decoradores de função, descritores e métodos, e de como um decorador de função implementado como uma classe, com `+__call__+`, também precisa implementar `+__get__+` se quiser funcionar com métodos de decoração e também com funções.

A https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma customização mais simples da criação de classes_)] (EN)
introduziu o método especial `+__set_name__+` e  inclui um exemplo de um
https://fpy.li/23-7[validating descriptor (_descritor de validação_)] (EN).

//[role="pagebreak-before"]
.Ponto de vista
****

[role="soapbox-title"]
O projeto de self

A((("attribute descriptors", "Soapbox discussion")))((("Soapbox sidebars", "explicit self argument")))((("explicit self argument"))) exigência de declarar `self` explicitamente como o primeiro argumento em métodos foi uma decisão de projeto controversa no Python.
Após 23 anos usando a linguagem, já estou acostumado com isso.
Acho que essa decisão é um exemplo de "pior é melhor" (_worse is better_):
a filosofia de projeto descrita pelo cientista da computação Richard P. Gabriel em
https://fpy.li/23-8["The Rise of Worse is Better" (_A Ascenção do Pior é Melhor_)] (EN).
A primeira prioridade dessa filosofia é "simplicidade", que Gabriel apresenta assim:

[quote]
____
O projeto deve ser simples, tanto na implementação quanto na interface.
É mais importante que a implementação seja simples, do que a interface.
A simplicidade é a consideração mais importante em um projeto.
____

O `self` explícito de Python incorpora essa filosofia de projeto.
A implementação é simples—até mesmo elegante—à custas da interface do usuário: uma assinatura de método como `def zfill(self, width):` não corresponde, visualmente, à invocação `label.zfill(8)`.

O Modula-3 introduziu essa convenção com o mesmo identificador, `self`.
Mas há uma diferença crucial: no Modula-3, interfaces são declaradas separadamente de sua implementação, e na declaração da interface o argumento `self` é omitido. Então, da perspectiva do usuário, um método aparece em uma declaração de interface com os mesmos parâmetros explícitos usados para invocá-lo.

Ao longo do tempo, as mensagens de erro de Python relacionadas a argumentos de métodos se tornaram mais claras.
Em um método definido pelo usuário com um argumento além de `self`, se o usuário invocasse
`obj.meth()`, Python 2.7 gerava:

[source]
----
TypeError: meth() takes exactly 2 arguments (1 given)
("TypeError: meth() recebe exatamente 2 argumentos (1 passado)"")
----

No Python 3, a confusa contagem de argumentos não é mencionada, e o argumento ausente é nomeado:

[source]
----
TypeError: meth() missing 1 required positional argument: 'x'
("TypeError: 1 argumento posicional obrigatório faltando em meth(): 'x'")
----

Além do uso de `self` como um argumento explícito, a exigência de qualificar cada acesso a atributos de instância com `self` também é criticada. Veja, por exemplo, o famoso post "Python Warts" (_As verrugas de Python_) de A. M. Kuchling (em https://fpy.li/23-9[archived] (EN)); o próprio Kuchling não se incomoda muito com o qualificador `self`, mas ele o menciona—provavelmente ecoando opiniões do grupo comp.lang.python.
Pessoalmente não me importo em digitar o qualificador `self`: é bom para distinguir variáveis locais de atributos. Minha questão é com o uso de `self` em comandos `def`.

Quem estiver triste com o `self` explícito de Python pode se sentir bem melhor após considerar a
https://fpy.li/23-10[semântica desconcertante] (EN) do `this` implícito em JavaScript. Guido tinha algumas boas razões para fazer `self` funcionar como funciona, e ele escreveu sobre elas em
https://fpy.li/23-11["Adding Support for User-Defined Classes" (_Adicionando Suporte a Classes Definidas pelo Usuário_)], um post em seu blog, _The History of Python_ ("A História de Python").
****

