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

[quote, Raymond Hettinger, guru e mantenedor do Python]
____

Aprender sobre descritores não apenas dá acesso a um conjunto maior de ferramentas,
também cria uma compreensão melhor sobre o funcionamento do Python e
uma apreciação pela elegância de seu design.footnote:[Raymond Hettinger, https://fpy.li/bv[«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, são descritores os campos de registros em um ORM (_Object Relational Mapping_, Mapeamento
Objeto-Relacional) como o _SQLAlchemy_ ou ORM do _Django_.
Nestes sistemas, os descritores controlam a conversão
e validação de dados dos atributos de objetos Python
para campos nas tabelas do banco de dados.

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 de descritor completo. 
Você ponde implementar apenas parte do protocolo de descritor, 
como ocorre com protocolos dinâmicos em geral.
Na verdade, a maioria dos
descritores que vemos em código real implementa apenas `+__get__+` e
`+__set__+`, e muitos têm só 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 do descritor permite que métodos operem como métodos vinculados ou funções, dependendo de como são acessados.

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

Nas próximas páginas((("attribute descriptors", "topics covered"))) vamos refatorar o exemplo da loja de alimentos orgânicos a granel, visto na <<prop_validation_sec>>, substituindo propriedades por descritores.
Isto 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>>, ficou muito mais simples graças ao
método especial `+__set_name__+`, adicionado ao protocolo de descritor no Python
3.6. Naquela 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__+` o exemplo ficou redundante.
Removi a classe `AutoStorage`, que aparecia na <<new_descr_type_sec>> pelo mesmo motivo.

[[validating_descriptor_sec]]
=== Descritor para 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 forma de evitar código repetitivo de _getters_ e _setters_, aplicando padrões de programação funcional.
Uma 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 preservar 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. Para usar um descritor, declaramos instâncias dele como atributos 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 [.underline]#weight# e [.underline]#price# são instâncias de `Quantity` vinculadas à classe `LineItem` (são atributos da classe), mas cada instância de `LineItem` também tem atributos `weight` e `price`, onde estes valores são armazenados na própria instância.
image::../images/flpy_2301.png[align="center", pdfwidth=10cm]

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árias peças.
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 aqui quando precisar.

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

Classe gerenciada (_managed class_):: 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 (_descriptor instance_):: Cada((("descriptor instances"))) instância de uma classe descritora declarada como um atributo de classe na 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 (_managed instance_):: Uma((("managed instances"))) instância da classe gerenciada. Neste exemplo, instâncias de `LineItem` são as instâncias gerenciadas (elas não aparecem no diagrama de classe).

Atributo de armazenamento (_storage attribute_):: Um((("storage attributes"))) atributo da instância gerenciada que guarda 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.

Atributo gerenciado (_managed attribute_):: Um((("managed attributes"))) atributo público na classe gerenciada que é controlado por uma instância de um descritor, com os valores preservados em atributos de armazenamento. 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`: [.underline]#weight# e [.underline]#price#. A engenhoca `LineItem` produz bugigangas retangulares que têm seus próprios atributos `weight` e `price`, onde aqueles valores são armazenados.
image::../images/flpy_2302.png[align="center", pdfwidth=11cm]

[role="pagebreak-before less_space"]
[[mgn_box1]]
.Apresentando 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, então 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.

Desenhei a MGN para tornar mais evidente a diferença entre classes e instâncias. Veja a <<mgn_diagram_demo>>. Na MGN, uma classe aparece como uma "engenhoca", (_mill_) uma máquina complicada que produz bugigangas (_gizmos_). Classes/engenhocas são máquinas com alavancas e mostradores. As bugigangas são as instâncias, e elas têm uma aparência mais simples. Quando este livro é produzido 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, elas têm uma lente de aumento para ler (_get_) os valores, e uma garra para escrever (_set_) os valores. Quando chegarmos às metaclasses, você me agradecerá por esses desenhos.
****

<<<
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 herdar de uma classe específica, basta implementar `+__get__+` ou `+__set__+`.

<2> Cada instância de `Quantity` terá um atributo `storage_name`: é o nome do atributo de armazenamento que vai preservar o valor nas instâncias gerenciadas.

<3> O `+__set__+` é invocado quando ocorre uma tentativa de atribuir um valor a um atributo gerenciado. Aqui, `self` é a instância do descritor `Quantity`, (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__+`; invocar `setattr(instance, self.storage_name, value)` 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 isto:

[source, python]
----
class Casa:
    quartos = Quantity('cômodos')
----

Na classe `Casa`, o atributo gerenciado é `quartos`, mas o atributo de armazenamento é `cômodos`.
Dada uma instância de `Casa` chamada `meu_lar`, acessar e modificar `meu_lar.quartos` passa pela instância do descritor `Quantity` vinculado a `quartos`, mas acessar e modificar `meu_lar.cômodos` não passa pelo 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 como `weight`, é acessado através da classe como
`LineItem.weight`, o método `+__get__+` do descritor recebe `None` no 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 original em _bulkfood_v1.py_ (no <<lineitem_class_v1>>).

O código no <<lineitem_class_v3>> evita 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 o caso real de uma pessoa que comprou uma enciclopédia sobre estatística 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 armazenar o valor de cada atributo gerenciado no próprio do descritor, mas é um erro. 

<<<
Em outras palavras, seria errado armazenar o valor no `+self.__dict__+`:

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

O correto é armazenar o valor no `+instance.__dict__+`:

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

Para entender por que a primeira opção não funciona, 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`.


==== Evitando repetir o nome do atributo gerenciado

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 o lado direito de uma atribuição é executado antes da variável
existir—como vimos no https://fpy.li/6[«Capítulo 6»] (vol.1). 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 de descritor agora suporta o método `+__set_name__+`. Veremos a seguir como usá-lo.

[NOTE]
====
Nomear automaticamente o atributo de armazenamento de um descritor era 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 não ter que repetir o nome do atributo ao criar uma instância de 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 de 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 atribuí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 só para gerenciar um par de atributos,
mas é importante perceber que agora abstraímos a lógica do descritor em uma unidade de código separada e reutilizável: a classe `Quantity`.

<<<
Normalmente não definimos um descritor no mesmo módulo em que ele é usado,
mas em um módulo auxiliar separado,
para ser usado por toda a aplicação—ou mesmo por muitas aplicações,
se estivermos desenvolvendo uma biblioteca 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 implementados 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 problema: 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.

Isto sugere 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 padrão _Template Method_ (Método Gabarito),
como descrito no clássico _Design Patterns_:

[quote]
____
O Método Gabarito define um algoritmo 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.]
____

No <<model_v5_abc>>, `+Validated.__set__+` é um método gabarito 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 que "preenche" gabarito `+__set__+`, definindo parte do seu comportamento.

Alex Martelli prefere chamar este padrão de projeto _Self-Delegation_ (Auto-Delegação),
e concordo que é um nome mais descritivo: a primeira linha de `+__set__+` auto-delega para
`validate`, ou seja, invoca outro método na mesma instância de uma subclasse concreta de `Validated`.footnote:[Slide #50 da palestra https://fpy.li/23-1[_Python Design Patterns_], de Alex Martelli. Altamente recomendada.]

As subclasses concretas de `Validated` neste exemplo são `Quantity` e `NonBlank`:

[[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 exigida pelo método abstrato `Validated.validate`.
<2> Se não sobrar nada após a remoção dos 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
chamados _descritores dominantes_, pois seu método `+__set__+` sobrescreve (isto é,
intercepta e impede) a atribuiçã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 ou 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.
Em uma classe sem descritores, ler um atributo
através de uma instância 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 cria o
atributo na instância, sem afetar a classe de forma alguma.

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

Usei algumas funções auxiliares definidas no <<descriptorkinds_aux_ex>> para observar as diferentes categorias de descritores, 
Sua lógica não é importante, mas elas são usadas nas invocações a `print_args` no <<descriptorkinds_ex>>.

[[descriptorkinds_aux_ex]]
.descriptorkinds.py: funções auxiliares para formatar e exibir objetos
====
[source, python]
----
include::../code/23-descriptor/descriptorkinds.py[tags=DESCR_KINDS_AUX]
----
====

Agora vamos criar três classes de descritores, e uma classe `Managed` onde os descritores são instalados.

<<<
[[descriptorkinds_ex]]
.descriptorkinds.py: para estudar os comportamentos 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, entã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]]
==== Descritor dominante

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 `+__get__+`

Propriedades e outros descritores dominantes, 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 na 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` obtém a instância do descritor na classe gerenciada.
<5> Definindo um atributo de instância chamado `over_no_get` direto no `+__dict__+` da instância.
<6> Agora aquele atributo de instância `over_no_get` oculta o descritor, mas só na leitura.
<7> Tentar atribuir um valor a `obj.over_no_get` continua passando pelo `+__set__+` do descritor.
<8> Mas na 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 o comportamento 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 de o descritor ser dominante ou não, 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 tratada 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 (_bound method_) quando invocada em uma
instância, porque todas as funções definidas pelo usuário têm 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 não são descritores 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 método vinculado: um invocável que
embrulha 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 https://fpy.li/cv[«Seção 7.8.2»] (vol.1).
Para um entendimento mais profundo deste 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> Seguindo a convenção, o `repr` de uma instância de `Text` parece uma chamada ao construtor
de `Text` que criaria uma instância igual.

<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: `function` e `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 àquela 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` invoca `+Text.reverse.__get__(word)+`,
devolvendo o método vinculado, mas sem invocá-lo.

<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, implementada na classe gerenciada.

<<<
O 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 automática do argumento `self` convencional.

A conversão de funções em métodos vinculados é um exemplo perfeito de como descritores são usados na 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 usar 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 não complicar demais:: 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` (não é permitido setar 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, você terá um descritor não-dominante, e setar 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 levantar `AttributeError` com uma mensagem adequada.footnote:[Python não é consistente nestas mensagens. Tentar modificar o atributo `c.real` de um número `complex` resulta em um `AttributeError: readonly attribute` (atributo somente para leitura), mas uma tentativa de mudar
`c.conjugate` (um método de `complex`) levanta um `AttributeError: 'complex' object attribute 'conjugate' is read-only` (o atributo 'conjugate' do objeto 'complex' é somente para leitura). Até "read-only" está escrito de maneira diferente nas mensagens 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. Assim, 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 <<lineitem_class_v4>>.

Um _cache_ eficiente pode ser feito só com `+__get__+`:: Implementando só o método `+__get__+`, você 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 de 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 https://fpy.li/82[«Seção 3.9»] (vol.1).].
O atributo de mesmo nome na instância vai ocultar o descritor, daí acessos subsequentes àquele atributo vão buscá-lo diretamente no `+__dict__+` da instância, sem acionar mais o `+__get__+` do descritor.
O decorador `@functools.cached_property` constrói um descritor não dominante.

Métodos não especiais podem 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 `meu_obj.o_método = 7`, significa que acessos posteriores a `o_método` através daquela instância irão obter o número ++7++—sem afetar a classe ou outras instâncias. Isto se aplica aos métodos especiais. O interpretador só acessa 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 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 poderem ser sobrescritos 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.

[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 ocultadas 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 e exclusão de descritores

[[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.]

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.
A <<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 é surpreendentemente 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
implementando um método `+__delete__+` na classe descritora. Omiti
deliberadamente falar de `+__delete__+`, porque seu uso no mundo
real é raro. Se você precisar disso, por favor consulte a seção
https://fpy.li/c7[«Implementando descritores»] na documentação do
_Modelo de dados de Python_. Escrever um exemplo com uma classe descritora
boba com `+__delete__+` fica como exercício para o leitor com excesso de tempo livre.



=== 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 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
instanciados com um `storage_name` explícito. A solução foi implementar o método especial `+__set_name__+` em
`Quantity`, para preservar 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.

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. 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 a uma classe se torna um método ao ser acessada através de uma instância, graças ao protocolo de descritores.

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 de 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://fpy.li/2j[«Modelo de dados»], o https://fpy.li/bv[«Guia de descritores»],
de Raymond Hettinger, é um recurso valioso-e parte da excelente
https://fpy.li/bw[«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 fez a apresentação
_Python's Object Model_ (O Modelo de Objetos do Python), tratando com
profundidade de propriedades e descritores: https://fpy.li/23-5[«slides»] e
https://fpy.li/23-6[«video»].

[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, entre
as quais destaco
_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.

<<<
A https://fpy.li/pep487[_PEP 487—Simpler customization of class creation_]
(Customização simplificada da criação de classes)
introduziu o método especial `+__set_name__+` e inclui um exemplo de um
https://fpy.li/23-7[«descritor de validação»].

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


*O design do parâmetro `self`*

A((("attribute descriptors", "Soapbox discussion")))((("Soapbox sidebars",
"explicit self argument")))((("explicit self argument"))) exigência de declarar
`self` explicitamente como o primeiro parâmetro em métodos foi uma decisão de
design controversa no Python. Eu me acostumei em menos de 20 anos!
Esta decisão é um exemplo de "pior é melhor"
(_worse is better_): a filosofia de design descrita pelo cientista da
computação Richard P. Gabriel em
https://fpy.li/23-8[_The Rise of Worse is Better_]
(A Ascensão do Pior é Melhor). A primeira prioridade dessa filosofia
é "simplicidade", que Gabriel apresenta assim:

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

O `self` explícito de Python incorpora esta filosofia de design.
A implementação é simples—até mesmo elegante—em prejuízo da usabilidade:
uma assinatura de método como `def zfill(self, width):` não corresponde, visualmente, à invocação `label.zfill(8)`.

A linguagem Modula-3, que Guido estudou antes de inventar o Python, introduziu esta convenção com o mesmo identificador, `self`.
Mas há uma diferença crucial: em 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 a mesma quantidade de parâmetros necessários 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)
(meth() recebe exatamente 2 argumentos (1 passado))
----

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

[source]
----
TypeError: meth() missing 1 required positional argument: 'x'
(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_ (Verrugas de Python) de A. M.
Kuchling (https://fpy.li/23-9[«cópia arquivada»] no _Internet Archive_);
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` na instrução `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»] do `this` implícito em JavaScript.
Guido teve 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), 
em seu blog, _The History of Python_ (A História de Python).
****

