[[ch_class_metaprog]]
== Metaprogramação de classes
:example-number: 0
:figure-number: 0

[quote, Brian W. Kernighan e P. J. Plauger, The Elements of Programming Style]
____
Todo mundo sabe que depurar um programa é duas vezes mais difícil que escrever o mesmo programa.
Mas daí, se você der tudo de si ao escrever o programa, como vai conseguir depurá-lo?footnote:[Citação extraída do capítulo 2, _Expression_ (Expressão), página 10, de _The Elements of Programming Style, Second Edition (NT: "Elementos de Estilo de Programação"; não encontramos edição traduzida deste livro.)]
____

Metaprogramação((("class metaprogramming", "benefits and drawbacks of"))) de classes é a arte de criar ou customizar classes durante a execução do programa.
Em Python, classes são objetos de primeira classe, então uma função pode criar uma nova classe a qualquer momento, sem usar a palavra-chave `class`,
e sem manipular código-fonte ou bytecodes.

Decoradores de classes também são funções, mas servem para inspecionar, modificar ou até substituir a classe decorada por outra classe.
Por fim, metaclasses são a ferramenta mais avançada para metaprogramação de classes: elas permitem a criação de categorias de classes inteiramente novas, com características especiais, como as classes base abstratas, que já vimos antes.

Metaclasses são poderosas, mas difíceis de justificar na prática, e ainda mais difíceis de entender direito. Decoradores de classe resolvem muitos dos mesmos problemas, e são mais fáceis de compreender. Além disso, Python 3.6 implementou a
https://fpy.li/pep487[_PEP 487—Simpler customization of class creation_]
(Customização simplificada da criação de classes), fornecendo métodos especiais para tarefas que antes exigiam metaclasses ou decoradores de classe.footnote:[Isso não quer dizer que a PEP 487 quebrou código que usava aqueles recursos, mas apenas que parte do código que utilizava decoradores de classe ou metaclasses antes de Python 3.6 pode agora ser refatorado para usar classes comuns, resultando em um código mais simples e possivelmente mais eficiente.]

Este capítulo apresenta as técnicas de metaprogramação de classes em ordem ascendente de complexidade.


[WARNING]
====
Esse é um tópico empolgante, e é fácil se deixar levar pelo entusiasmo.
Então preciso deixar aqui esse conselho.

Em nome da legibilidade e facilidade de manutenção, você provavelmente deveria evitar as técnicas descritas neste capítulo em aplicações.

Por outro lado, caso queira escrever o próximo grande framework de Python, estas são suas ferramentas de trabalho.
====

=== Novidades neste capítulo

Todo((("class metaprogramming", "significant changes to"))) o código do capítulo _Metaprogramação de Classes_ da primeira edição do _Python Fluente_ ainda funciona corretamente.
Entretanto, alguns dos exemplos antigos podem ser bastante simplificados com recursos surgidos desde o Python 3.6.

Substituí aqueles exemplos por outros, enfatizando os novos recursos de metaprogramação ou acrescentando novos requisitos para justificar o uso de técnicas mais avançadas.
Alguns destes exemplos novos se valem de dicas de tipo para fornecer fábricas de classes similares ao decorador `@dataclass` e a `typing.NamedTuple`.

A <<metaclases_real_world_sec>> é nova, trazendo algumas considerações de alto nível sobre a aplicabilidade das metaclasses.

[TIP]
====
Algumas das melhores refatorações tratam de remover código tornado redundante por formas modernas e mais simples de resolver o mesmo problema.
Isso se aplica tanto a código em produção quanto a livros.
====

Vamos começar revisando os atributos e métodos definidos no _Modelo de Dados_ de Python para todas as classes.

[[anatomy_of_classes_sec]]
=== Classes como objetos

Como((("class metaprogramming", "classes as objects"))) acontece com a maioria
dos elementos da linguagem Python, classes também são objetos. Toda classe
tem alguns atributos definidos no _Modelo de Dados_ de Python, documentados na
seção https://fpy.li/bt[_Atributos Especiais_] do capítulo _Tipos Embutidos_ da
_Biblioteca Padrão de Python_.
Três((("&#x005F;&#x005F;class&#x005F;&#x005F;")))((("&#x005F;&#x005F;name&#x005F;&#x005F;")))((("&#x005F;&#x005F;mro&#x005F;&#x005F;")))
destes atributos já apareceram várias vezes no livro: `+__class__+`,
`+__name__+`, e `+__mro__+`. Outros atributos de classe padrão são:

`+cls.__bases__+`:: A((("cls.&#x005F;&#x005F;bases&#x005F;&#x005F;"))) tupla de classes base da classe.

`+cls.__qualname__+`:: O((("cls.&#x005F;&#x005F;qualname&#x005F;&#x005F;"))) nome qualificado de uma classe ou função, que é um caminho pontuado, desde o escopo global do módulo até a definição da classe. Isso é relevante quando a classe é definida dentro de outra classe.
Por exemplo, na classe https://fpy.li/24-2[`Ox`] que ilustra um modelo na da documentação do Django, há uma classe aninhada chamada `Meta`. O `+__qualname__+` de `Meta` é `Ox.Meta`, mas seu `+__name__+` é apenas `Meta`.
A especificação para este atributo está na
https://fpy.li/24-3[_PEP 3155—Qualified name for classes and functions_] (Nome qualificado para classes e funções).

`+cls.__subclasses__()+`:: Este((("cls.&#x005F;&#x005F;subclasses&#x005F;&#x005F;()"))) método devolve uma lista das subclasses imediatas da classe.
A implementação usa referências fracas, para evitar referências circulares entre a superclasse e suas subclasses.
Cada subclasse tem referências fortes para suas superclasses em seu atributo `+__bases__+`.
O método lista as subclasses na memória naquele momento. Subclasses em módulos ainda não importados não aparecerão no resultado.

`cls.mro()`:: O((("cls.mro()"))) interpretador invoca este método quando está criando uma classe, para obter a tupla de superclasses armazenada no atributo `+__mro__+` da classe.
Uma metaclasse pode  sobrescrever este método, para customizar a ordem de resolução de métodos da classe em construção.

[TIP]
====
Nenhum dos atributos mencionados nesta seção aparece na lista devolvida pela função `dir(…)`.
====

Agora, se uma classe é um objeto, o que é a classe de uma classe?

<<<
=== `type`: a fábrica de classes embutida

Normalmente((("class metaprogramming", "built-in class factory")))
pensamos em `type` como uma função que devolve a classe de um objeto,
porque é isso que `type(my_object)` faz: devolve `+my_object.__class__+`.

Entretanto, `type` é uma classe que cria uma nova classe, quando invocada com três argumentos.

Considere esta classe simples:

[source, python]
----
class MyClass(MyMixin, MySuperClass):
    x = 42

    def x2(self):
        return self.x * 2
----

Usando o construtor `type`, podemos criar uma classe idêntica durante a execução, com o seguinte código:

[source, python]
----
MyClass = type('MyClass',
               (MyMixin, MySuperClass),
               {'x': 42, 'x2': lambda self: self.x * 2},
          )
----

Quando Python lê uma instrução `class`, invoca `type` para construir um objeto classe assim:

[source, python]
----
new_class = type(name, bases, cls_dict)
----

Onde os parâmetros são:

`name`::
    O identificador que aparece após a palavra-chave `class`, por exemplo, `MyClass`.

<<<

`bases`::
    A tupla de superclasses passada entre parênteses após o identificador da classe, ou
    `(object,)`, caso nenhuma superclasse seja mencionada na instrução `class`.
`cls_dict`::
    Um mapeamento entre nomes de atributos e valores.
    Invocáveis se tornam métodos, como vimos na <<methods_are_descriptors_sec>>.

[NOTE]
====

O construtor `type` aceita argumentos nomeados opcionais, que são ignorados por
`type`, mas são passados para `+__init_subclass__+`, que deve
consumí-los. Vamos estudar esse método especial na
<<enhancing_with_init_subclass_sec>>, mas tratarei o uso de argumentos
nomeados. Para saber mais sobre isso, por favor leia a
https://fpy.li/pep487[_PEP 487_] sobre formas modernas de customizar a criação de classes.

====

A classe `type` é uma((("metaclasses", "definition of term"))) _metaclasse_: uma classe que cria classes.
Em outras palavras, instâncias da classe `type` são classes.
A biblioteca padrão contém algumas outras metaclasses, mas `type` é a  default:

[source, python]
----
>>> type(7)
<class 'int'>
>>> type(int)
<class 'type'>
>>> type(OSError)
<class 'type'>
>>> class Whatever:
...     pass
...
>>> type(Whatever)
<class 'type'>
----

Vamos criar metaclasses customizadas na <<metclass101_sec>>.

Agora, vamos usar a classe embutida `type` para criar uma função que constrói classes.


=== Uma função fábrica de classes

A((("class metaprogramming", "class factory function", id="CMfacfun24"))) biblioteca padrão contém uma função fábrica de classes que já apareceu várias vezes aqui: `collections.namedtuple`.
No https://fpy.li/5[«Capítulo 5»] (vol.1) também vimos `typing.NamedTuple` e `@dataclass`.
Estas fábricas de classe usam técnicas que veremos neste capítulo.

Vamos começar com uma fábrica muito simples, para classes de objetos mutáveis—a substituta mais simples possível de `@dataclass`.

Suponha que eu esteja escrevendo uma aplicação para um _pet shop_,
e queira armazenar dados sobre cães como registros simples.
Mas não quero escrever código padronizado como esse:

[source, python]
----
class Dog:
    def __init__(self, name, weight, owner):
        self.name = name
        self.weight = weight
        self.owner = owner
----

Tédio: `name`, `name`, `name`, `weight`, `weight`, `weight`... E ainda falta um bom `repr`:

[source, python]
----
>>> rex = Dog('Rex', 30, 'Bob')
>>> rex
<__main__.Dog object at 0x2865bac>
----

Inspirados por `collections.namedtuple`, vamos criar uma `record_factory`,
que cria classes simples como `Dog` dinamicamente. O <<record_factory_demo>> mostra como ela deve funcionar.

[[record_factory_demo]]
.Testando `record_factory`, uma fábrica de classes simples
====
[source, python]
----
include::../code/24-class-metaprog/factories.py[tags=RECORD_FACTORY_DEMO]
----
====
<1> A fábrica pode ser invocada como `namedtuple`: nome da classe, seguido de uma string contendo os nomes dos atributos separados por espaços.
<2> Um `repr` agradável.
<3> Instâncias são iteráveis, então elas podem ser convenientemente desempacotadas em uma atribuição...
<4> ...ou quando são passadas para funções como `format`.
<5> As instâncias são mutáveis.
<6> A classe recém-criada herda de `object`—não tem relação com nossa fábrica.

O código para `record_factory` está no <<record_factory_ex>>.footnote:[Agradeço ao meu amigo J. S. O. Bueno por ter contribuído com esse exemplo.]

[[record_factory_ex]]
.record_factory.py: uma classe fábrica simples
====
[source, python]
----
include::../code/24-class-metaprog/factories.py[tags=RECORD_FACTORY]
----
====
<1> O usuário pode fornecer os nomes dos campos como uma string única ou como um iterável de strings.
<2> Aceita argumentos como os dois primeiros de `collections.namedtuple`; devolve `type`—isto é, uma classe.
<3> Cria uma tupla de nomes que será o `+__slots__+` da nova classe.
<4> Esta função se tornará o método `+__init__+` na nova classe. Ela aceita argumentos posicionais e/ou nomeados.footnote:[Não acrescentei dicas de tipo aos argumentos porque os tipos reais são `Any`. Escrevi a dica do tipo devolvido (`None`) para que o Mypy não deixe de checar o método.]
<5> Produz os valores dos campos na ordem dada por `+__slots__+`.
<6> Produz um `repr` agradável, iterando sobre `+__slots__+` e `self`.
<7> Monta um dicionário de atributos de classe.
<8> Cria e devolve a nova classe, invocando o construtor de `type`.
<9> Converte `names` separados por espaços ou vírgulas em uma lista de `str`.

O <<record_factory_ex>> é a primeira vez que vemos `type` em uma dica de tipo:
indica que `record_factory` devolve uma classe.

A última linha de `record_factory` no <<record_factory_ex>> cria uma classe cujo
nome é o valor de `cls_name`, com `object` como sua única classe base imediata,
e um espaço de nomes (_namespace_) carregado com `+__slots__+` e os
métodos de instância `+__init__+`, `+__iter__+`, e
`+__repr__+`.

Poderíamos ter dado qualquer outro nome ao atributo de classe `+__slots__+`, mas
daí teríamos que implementar `+__setattr__+` para validar os nomes dos atributos
em uma atribuição, porque em nossas classes similares a registros queremos que o
conjunto de atributos seja sempre o mesmo e na mesma ordem. Entretanto,
lembre-se de que a principal característica de `+__slots__+` é economizar memória
quando estamos lidando com milhões de instâncias, e que usar `+__slots__+` traz
algumas desvantagens, discutidas na https://fpy.li/28[«Seção 11.11»] (vol.2).

[WARNING]
====

Instâncias de classes criadas por `record_factory` não são serializáveis—isto
é, elas não podem ser exportadas em formato binário pela função `dump` do módulo `pickle`.
Resolver este problema está além do escopo deste exemplo, cujo objetivo é
mostrar a classe `type` funcionando em um caso de uso simples. Para uma solução
completa, estude o código-fonte de `collections.namedtuple`; procure pela
palavra "pickling".

====

Vamos ver agora como emular fábricas de classes mais modernas, como `typing.NamedTuple`, que recebe uma classe definida pelo usuário, escrita com a instrução `class`, e a enriquece automaticamente com mais funcionalidade.((("", startref="CMfacfun24")))


[[enhancing_with_init_subclass_sec]]
=== Apresentando `+__init_subclass__+`

Tanto((("class metaprogramming", "&#x005F;&#x005F;init_subclass&#x005F;&#x005F;", id="CMinitsub24", secondary-sortas="init")))((("&#x005F;&#x005F;init&#x005F;subclass&#x005F;&#x005F;", id="initsub24"))) `+__init_subclass__+` quanto `+__set_name__+` foram propostos na
https://fpy.li/pep487[_PEP 487—Simpler customization of class creation_].
Falamos pela primeira vez do método especial para descritores `+__set_name__+` na <<auto_storage_sec>>.
Agora vamos estudar `+__init_subclass__+`.

No https://fpy.li/5[«Capítulo 5»] (vol.1), vimos como `typing.NamedTuple` e `@dataclass` usam a sintaxe de `class` para definir atributos em uma
classe, que então é enriquecida com métodos úteis, como `+__init__+`, `+__repr__+`, `+__eq__+`,
etc.

Ambas as fábricas de classes leem as dicas de tipo na instrução `class` do usuário para enriquecer a classe. Estas dicas de tipo também permitem que checadores de tipos estáticos validem código que define ou lê aqueles atributos.
Entretanto, `NamedTuple` e `@dataclass` não usam as dicas de tipo para validação de atributos durante a execução.
A classe `Checked`, no próximo exemplo, faz isso.

[NOTE]
====

Não é possível suportar toda dica de tipo estática concebível para checagem de
tipos durante a execução. Entretanto, alguns tipos que são também classes
concretas podem ser usados com `Checked`. Isto inclui tipos simples, usados com
frequência para o conteúdo de campos, como  `str`, `int`, `float`, e `bool`, bem
como listas destes tipos.

====

O <<checked_demo1_ex>> mostra como usar `Checked` para criar uma classe `Movie`.

[[checked_demo1_ex]]
.initsub/checkedlib.py: doctest para a criação de uma subclasse `Movie` de `Checked`
====
[source, python]
----
include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFINITION]
----
====
<1> `Movie` herda de `Checked`—que definiremos mais tarde, no <<ex_checked_class_top>>.
<2> Cada atributo é anotado com um construtor. Aqui usei tipos embutidos.
<3> Instâncias de `Movie` devem ser criadas usando argumentos nomeados.
<4> Em troca, temos um `+__repr__+` agradável.


<<<
Os construtores usados como dicas de tipo podem ser qualquer invocável que receba zero ou um argumento, e devolva um valor adequado ao tipo do campo pretendido ou rejeite o argumento, gerando um `TypeError` ou um `ValueError`.

Usar tipos embutidos para as anotações no <<checked_demo1_ex>> significa que os valores devem ser aceitáveis pelo construtor do tipo.
Para `int`, isso significa qualquer `x` tal que `int(x)` devolva um `int`.
Para `str`, qualquer coisa serve durante a execução, pois `str(x)` funciona com qualquer `x` no
Python.footnote:[Isso é verdade para qualquer objeto, exceto quando sua classe sobrescreve os métodos `+__str__+` ou `+__repr__+`, herdados de `object`, por uma implementação que não funcione.]

Quando chamado sem argumentos, o construtor deve devolver um valor default de seu tipo.footnote:[Essa solução evita usar `None` como default. Evitar valores nulos é uma https://fpy.li/24-5[boa ideia]. Em geral, eles são difíceis de evitar, mas em alguns casos isso é fácil. Tanto no Python quanto no SQL, prefiro representar dados ausentes em um campo de texto como uma string vazia em vez de `None` ou `NULL`. Aprender Go reforçou essa ideia: em Go, variáveis e campos struct de tipos primitivos são inicializados por default com um "valor zero" (_zero value_). Se você estiver curiosa, veja a página https://fpy.li/24-6["Zero values" ("Valores zero")] no _Tour of Go_ ("Tour do Go") online]

Esse é o comportamento padrão de construtores embutidos no Python:

[source, python]
----
>>> int(), float(), bool(), str(), list(), dict(), set()
(0, 0.0, False, '', [], {}, set())
----

Em uma subclasse de `Checked` como `Movie`, parâmetros ausentes criam instâncias com os valores default devolvidos pelos construtores dos campos. Por exemplo:

[source, python]
----
include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_DEFAULTS]
----

Os construtores são usados para validação durante a instanciação, e quando um atributo é definido diretamente em uma instância:

[source, python]
----
include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=MOVIE_TYPE_VALIDATION]
----

<<<
.Subclasses de `Checked` e a verificação estática de tipos
[WARNING]
====
Em um arquivo de código-fonte _.py_ contendo uma instância `movie` da classe `Movie`, como definida no <<checked_demo1_ex>>, o Mypy marca essa atribuição como um erro de tipo:

[source, python]
----
movie.year = 'MCMLXXII'
----

Entretanto, o Mypy não consegue detectar erros de tipo nesta chamada ao construtor:

[source, python]
----
megahit = Movie(title='Avatar', year='MMIX')
----

Isso porque `Movie` herda `+Checked.__init__+`,
e a assinatura daquele método deve aceitar qualquer argumento nomeado, para suportar classes arbitrárias definidas pelo usuário.

Por outro lado, se você declarar um campo de uma subclasse de `Checked` com a dica de tipo
`list[float]`, o Mypy pode sinalizar atribuições de listas com tipos incompatíveis, mas `Checked` vai ignorar o parâmetro de tipo e tratá-lo como igual a `list`.
====

Vamos ver agora a implementação de _checkedlib.py_.
A primeira classe é o descritor `Field`, como mostra o <<checked_field_ex>>.

[[checked_field_ex]]
.initsub/checkedlib.py: a classe descritora `Field`
====
[source, python]
----
include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_FIELD]
----
====
<1> Lembre-se, desde o Python 3.9, o tipo `Callable` para anotações é a ABC em
`collections.abc`, e não `typing.Callable` que foi descontinuado.
<2> Essa é a dica de tipo mínima para `Callable` mínima; o parâmetro de tipo e o tipo devolvido são implicitamente `Any`.
<3> Para checagem durante a execução, usamos o embutido `callable`.footnote:[Na minha opinião, `callable` deveria se tornar adequado para dicas de tipo. Em 6 de maio de 2021, quando essa nota foi escrita, essa ainda era uma https://fpy.li/24-7[questão aberta].] O teste contra `type(None)` é necessário porque Python entende `None` em um tipo como `NoneType`, a classe de `None` (e portanto invocável), mas esse é um construtor inútil, que apenas devolve `None`.
<4> Se `+Checked.__init__+` definir `value` como `\...` (o objeto embutido `Ellipsis`), invocamos o construtor sem argumentos.
<5> Caso contrário, invocamos o `constructor` com o `value` fornecido.
<6> Se `constructor` levantar uma destas exceções, levantamos um `TypeError` com uma mensagem útil, incluindo os nomes do campo e do construtor; por exemplo, `'MMIX' não é compatível com year:int`.
<7> Se nenhuma exceção for levantada, o `value` é armazenado no `+instance.__dict__+`.

Em `+__set__+`, precisamos capturar `TypeError` e `ValueError`, pois os construtores embutidos podem levantar qualquer uma ou outra, dependendo do argumento.
Por exemplo, `float(None)` levanta `TypeError`, mas `float('A')` levanta `ValueError`.
Por outro lado, `float('8')` não causa qualquer erro, e devolve `8.0`.
Vamos combinar que, neste exemplo simples, este é um recurso, não um bug.

<<<

[TIP]
====
Na <<auto_storage_sec>>, vimos o conveniente método especial `+__set_name__+` para descritores.
Não precisamos disso na classe `Field`, porque os descritores não são instanciados no código-fonte cliente; o usuário declara tipos que são construtores, como visto na classe `Movie` (no <<checked_demo1_ex>>).
Em vez disso, as instâncias do descritor `Field` são criadas durante a execução, pelo método
`+Checked.__init_subclass__+`, que veremos no <<ex_checked_class_top>>.
====

Vamos agora nos concentrar na classe `Checked`, que dividi em duas listagens. O <<ex_checked_class_top>> mostra a parte inicial da classe, incluindo os métodos mais importantes para este exemplo.
O restante dos métodos está no <<checked_class_bottom_ex>>.

[[ex_checked_class_top]]
.initsub/checkedlib.py: os métodos mais importantes da classe `Checked`
====
[source, python]
----
include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_TOP]
----
====

<1> Escrevi este método de classe para isolar a chamada a
`typing.get_type_hints` do resto da classe. Se precisasse suportar apenas
versões de Python ≥ 3.10, invocaria `inspect.get_annotations` em vez disso.
Reveja a https://fpy.li/2a[«Seção 15.5.1»] (vol.2) para uma discussão dos problemas com
essas funções.

<2> `+__init_subclass__+` é chamado sempre que uma subclasse da classe atual é
definida. Ele recebe aquela nova subclasse como seu primeiro argumento—e por
isso nomeei o argumento `subclass` em vez do habitual `cls`. Para mais
informações sobre isso, veja a seguir a caixa _<<init_subclass_not_typical_box>>_.

<3> `+super().__init_subclass__()+` não é estritamente necessário, mas é
serve para suportar superclasses que implementem `+.__init_subclass__()+` na
mesma árvore de herança. Veja a https://fpy.li/cw[«Seção 14.4»] (vol.2).

<4> Itera sobre `name` e `constructor` em cada campo...

<5> ...criando um atributo em `subclass` com aquele `name` vinculado a um
descritor `Field`, parametrizado com `name` e `constructor`.

<6> Para cada `name` nos campos da classe...

<7> ...obtém o `value` correspondente de `kwargs` e o remove de `kwargs`. Usar
`\...` (o objeto `Ellipsis`) como default nos permite distinguir entre
argumentos com valor `None` de argumentos ausentes.footnote:[Como mencionado em
<<good_poison_pill_tip>>, o objeto `Ellipsis` é um valor sentinela conveniente e
seguro. Ele existe no Python há muito tempo, mas recentemente mais usos têm sido
encontrados para ele, como vemos nas dicas de tipo e no NumPy.]

<8> Invocar `setattr` aciona `+Checked.__setattr__+`
(<<checked_class_bottom_ex>>).

<9> Se houver itens remanescentes em `kwargs`, seus nomes não correspondem a
qualquer dos campos declarados, e `+__init__+` vai falhar.

<10> Este erro é informado por `+__flag_unknown_attrs+`, listado no
<<checked_class_bottom_ex>>. Ele recebe um argumento `*names` com os nomes de
atributos desconhecidos. Usei um único asterisco em `*kwargs`, para passar suas
chaves como uma sequência de argumentos, sem passar os valores.


[[init_subclass_not_typical_box]]
.`+__init_subclass__+` não é um método de classe típico
****

O primeiro argumento que Python passa para `+__init_subclass__+` é uma classe.
Entretanto, esta não é a classe onde `+__init_subclass__+` está definido,
mas sim uma subclasse que herda daquela classe.
Este comportamento é diferente de `+__new__+` e de métodos de classe decorados com `@classmethod`,
onde o primeiro argumento é sempre a própria classe.

Então `+__init_subclass__+` não é um método de classe no sentido usual, e considero enganoso nomear seu primeiro argumento `cls`.
Prefiro o nome `subcls`. A
https://fpy.li/c2[«documentação de `+__init_suclass__+`»] chama o argumento de `cls`,
mas explica: "...chamado sempre que se cria uma subclasse da classe que o contém.
`cls` é então a nova subclasse..."

****

Vamos examinar os métodos restantes da classe `Checked`,
continuando do <<ex_checked_class_top>>.
Observe que prefixei os nomes dos métodos `+_fields+` e `+_asdict+` com `+_+`, pela mesma razão pela qual isso é feito na API de `collections.namedtuple`: reduzir a possibilidade de colisões de nomes com nomes de campos definidos pelo usuário.

[[checked_class_bottom_ex]]
.initsub/checkedlib.py: métodos restantes da classe `Checked`
====
[source, python]
----
include::../code/24-class-metaprog/checked/initsub/checkedlib.py[tags=CHECKED_BOTTOM]
----
====
<1> Intercepta qualquer tentativa de definir um atributo de instância. Isto é necessário para evitar a definição de um atributo desconhecido.

<2> Se o `name` do atributo é conhecido, busca o `descriptor` correspondente.

<3> Normalmente não é preciso invocar o `+__set__+` do descritor explicitamente. Aqui é necessário porque `+__setattr__+` intercepta todas as tentativas de definir um atributo em uma instância, mesmo na presença de um descritor dominante, tal como `Field`.

<4> Caso contrário, o atributo `name` é desconhecido, e uma exceção será levantada por
`+__flag_unknown_attrs+`.

<5> Cria uma mensagem de erro útil, listando todos os argumentos inesperados, e levanta `AttributeError`.
Este é um raro exemplo do tipo especial `NoReturn`, tratado na https://fpy.li/8f[«Seção 8.5.12»] (vol.1).

<6> Cria um `dict` a partir dos atributos de um objeto `Movie`. Eu chamaria este método de
`+_as_dict+`, mas segui a convenção iniciada com o método `+_asdict+` em `collections.namedtuple`.

<7> Implementar um `+__repr__+` agradável é a principal razão para ter `_asdict` neste exemplo.

O exemplo `Checked` mostra como tratar descritores dominantes ao implementar `+__setattr__+` para bloquear a definição arbitrária de atributos após a instanciação.
É possível debater se vale a pena implementar `+__setattr__+` neste exemplo.
Sem ele, definir `movie.director = 'Greta Gerwig'` funcionaria, mas o atributo `director` não seria verificado de forma alguma, não apareceria no `+__repr__+` nem seria incluído no `dict` devolvido por `+_asdict+`—ambos definidos no <<checked_class_bottom_ex>>.

Em _record_factory.py_ (no <<record_factory_ex>>), solucionei essa questão usando o atributo de classe `+__slots__+`.
Entretanto, essa solução mais simples não é viável aqui, como explicado a seguir.

[[why_cannot_config_slots_sec]]
==== Por que `+__init_subclass__+` não pode configurar `+__slots__+`?

O((("&#x005F;&#x005F;slots&#x005F;&#x005F;"))) atributo `+__slots__+` só tem efeito quando é um dos itens do espaço de nomes da classe passado para `+type.__new__+`.
Acrescentar `+__slots__+` a uma classe existente não funciona.
Python invoca `+__init_subclass__+` apenas após a classe ser criada—neste ponto, é tarde demais para configurar `+__slots__+`.
Um decorador de classes também não pode configurar `+__slots__+`, pois ele é aplicado ainda mais tarde que `+__init_subclass__+`.
Vamos explorar essas questões de sincronia na <<import_v_runtime_sec>>.

<<<
Para configurar `+__slots__+` durante a execução, nosso código precisa criar o espaço de nomes da classe a ser passado como último argumento de `+type.__new__+`.
Para fazer isso, podemos escrever uma função fábrica de classes, como _record_factory.py_, ou optar pelo caminho radical, e implementar uma metaclasse.
Veremos como configurar `+__slots__+` dinamicamente na <<metclass101_sec>>.

Antes da https://fpy.li/pep487[_PEP 487_] simplificar a customização da criação de classes com
`+__init_subclass__+`, no Python 3.7, uma funcionalidade similar só poderia ser implementada usando um decorador de classe.
Pergunte-me como.((("", startref="initsub24")))((("", startref="CMinitsub24")))


=== Um decorador de classes

Um((("class metaprogramming", "enhancing classes with class decorators",
id="CMcdecorator24")))((("decorators and closures", "enhancing classes with
class decorators", id="DACcdeco24"))) decorador de classes tem comportamento
semelhante a um decorador de funções: recebe uma classe decorada
como argumento, e devolve uma classe para substituir a classe decorada.
Decoradores de classe normalmente devolvem a própria classe decorada, após
injetar novos métodos nela. Talvez, a razão
mais comum para escolher um decorador de classes, em vez do 
`+__init_subclass__+`, é evitar interferência com outros mecanismos de classes,
como herança e metaclasses. Esta justificativa aparece no resumo da
https://fpy.li/24-9[_PEP 557–Data Classes_] para explicar
por que ela foi implementada como um decorador de classes.

Nesta seção vamos estudar _checkeddeco.py_, que oferece a mesma funcionalidade de _checkedlib.py_, mas usando um decorador de classe.
Como sempre, começamos examinando um exemplo de uso, extraído dos doctests em _checkeddeco.py_ (no <<checkeddeco_demo1_ex>>).

[[checkeddeco_demo1_ex]]
.checkeddeco.py: criando uma classe `Movie` decorada com `@checked`
====
[source, python]
----
include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=MOVIE_DEFINITION]
----
====

A única diferença entre o <<checkeddeco_demo1_ex>> e o <<checked_demo1_ex>> é a forma como a classe `Movie` é declarada: ela é decorada com `@checked` em vez de ser uma subclasse de `Checked`.
Fora isso, o comportamento externo é o mesmo, incluindo a validação de tipo e a atribuição de valores default, apresentados após
o <<checked_demo1_ex>>, na <<enhancing_with_init_subclass_sec>>.

Vamos olhar agora para a implementação de _checkeddeco.py_.
As importações e a classe `Field` são as mesmas de _checkedlib.py_, listadas no <<checked_field_ex>>.
Em _checkeddeco.py_ não há qualquer outra classe, apenas funções.

A lógica antes implementada em `+__init_subclass__+` agora é parte da função `checked`—o decorador de classes listado no <<checkeddeco_decorators_ex>>.

[[checkeddeco_decorators_ex]]
.checkeddeco.py: o decorador de classes
====
[source, python]
----
include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_DECORATOR]
----
====

<1> Lembre-se de que classes são instâncias de `type`. Estas dicas de tipo indicam
que este é um decorador de classes: recebe uma classe e devolve
uma classe.

<2> `+_fields+` agora é uma função de alto nível definida mais tarde no módulo
(<<checkeddeco_methods_ex>>).

<3> Troca cada atributo devolvido por `+_fields+` por uma instância do
descritor `Field` é o que `+__init_subclass__+` fazia no
<<ex_checked_class_top>>. Aqui há mais trabalho a ser feito...

<4> Cria um método de classe a partir de `+_fields+`, e o adiciona à classe
decorada. O comentário `type: ignore` é necessário, porque o Mypy reclama que
`type` não tem um atributo `_fields`.

<5> Funções ao nível do módulo, que se tornarão métodos de instância da classe
decorada.

<6> Adiciona cada um dos `instance_methods` a `cls`.

<7> Devolve a `cls` decorada, cumprindo o contrato básico de um decorador de
classes.

Todas as funções no primeiro nível de _checkeddeco.py_ estão prefixadas com um sublinhado, exceto o decorador `checked`.
Essa convenção de nomenclatura faz sentido por duas razões:

* `checked` é parte da interface pública do módulo _checkeddeco.py_, as outras funções não.
* As funções no <<checkeddeco_methods_ex>> serão injetadas na classe decorada,
e o `+_+` inicial reduz as chances de um conflito de nomes com atributos e métodos definidos pelo usuário na classe decorada.

O restante de _checkeddeco.py_ está listado no <<checkeddeco_methods_ex>>.
Aquelas funções no nível do módulo contêm o mesmo código dos métodos correspondentes na classe `Checked` de _checkedlib.py_.
Elas foram explicadas no <<ex_checked_class_top>> e no <<checked_class_bottom_ex>>.

Observe que a função `+_fields+` tem dois papéis em _checkeddeco.py_.
Ela é usada como uma função normal na primeira linha do decorador `checked` e será também injetada como um método de classe na classe decorada.

[[checkeddeco_methods_ex]]
.checkeddeco.py: os métodos que serão injetados na classe decorada
====
[source, python]
----
include::../code/24-class-metaprog/checked/decorator/checkeddeco.py[tags=CHECKED_METHODS]
----
====

O módulo _checkeddeco.py_ implementa um decorador de classes simples, mas usável.
O `@dataclass` de Python faz mais.
Ele suporta várias opções de configuração, acrescenta métodos à classe decorada, trata ou avisa sobre conflitos com métodos definidos pelo usuário na classe decorada, e até percorre o `+__mro__+` para coletar atributos definidos pelo usuário declarados em superclasses da classe decorada.
O https://fpy.li/24-10[«código-fonte»] do pacote `dataclasses` no Python 3.9 tem mais de 1200 linhas.

Para fazer metaprogramação de classes, precisamos saber quando o interpretador Python avalia cada bloco de código durante a criação de uma classe.
É disso que falaremos a seguir.((("", startref="DACcdeco24")))((("", startref="CMcdecorator24")))

[[import_v_runtime_sec]]
=== O que acontece quando: importação versus execução

Programadores Python((("class metaprogramming", "import time versus runtime",
id="CMimport24")))((("import time versus runtime"))) falam de "momento da
importação" (_import time_) versus "momento de execução" (_run time_), mas estes
termos não têm definições precisas e há uma zona cinzenta entre eles.

No momento da importação, o interpretador:

. Analisa o código-fonte do módulo _.py_ em uma passagem, da primeira até a última linha. É aqui que um `SyntaxError` pode ocorrer.
. Compila o _bytecode_ a ser executado.
. Executa o código no nível superior do módulo compilado.

Se existir um arquivo _.pyc_ atualizado no `+__pycache__+` local,
a análise e a compilação são omitidas, pois o _bytecode_ está pronto para ser executado.

Apesar de a análise e a compilação serem definitivamente atividades de "importação",
outras coisas podem acontecer durante este processo,
pois quase todas as instruções no Python são executáveis,
pois podem rodar código do usuário
e modificar o estado do programa.

Em especial, a instrução `import` não é meramente uma
declaração, como é em Java, onde ela serve para informar
o compilador sobre os pacotes necessários.
Em Python, `import` serve para isso, mas também carrega e executa todo o código no
nível superior de um módulo, quando ele é importado para o processo Python pela
primeira vez. Importações posteriores do mesmo módulo usarão um _cache_, e então
o único efeito será a vinculação dos objetos importados a nomes no módulo
cliente. O código executado em consequência de um `import` pode fazer qualquer
coisa, incluindo ações típicas da "execução", como escrever em um arquivo de log
ou conectar-se a um banco de dados.footnote:[Não estou dizendo que é uma boa
ideia abrir uma conexão com um banco de dados só porque o módulo foi importado,
apenas apontando que isso pode ser feito.] Por isso a fronteira entre a
"importação" e a "execução" é difusa: `import` pode acionar todo tipo de
comportamento de "execução", porque a instrução `import` e a função embutida
`+__import__()+` podem ser usadas dentro de qualquer função normal.

Tudo isso é bastante abstrato e sutil, então vamos fazer alguns experimentos para ver o que acontece, e quando.


[[evaldemo_sec]]
==== Experimentos com as etapas de avaliação

Considere um script _evaldemo.py_, que usa um decorador de classes, um descritor e uma fábrica de classes baseada em `+__init_subclass__+`, todos definidos em um módulo _builderlib.py_.
Os módulos usados têm várias chamadas a `print`, para revelar o que acontece por baixo dos panos. Fora isso, eles não fazem nada de útil. O objetivo destes experimentos é observar a ordem na qual essas chamadas a `print` acontecem.

[WARNING]
====

Aplicar um decorador de classes e uma fábrica de classes com
`+__init_subclass__+` juntos, em uma única classe, é provavelmente um sinal de
excesso de engenharia ou de desespero. Esta combinação incomum é útil nestes
experimentos, para comparar em que momento um decorador de
classes e `+__init_subclass__+` alteram a classe.

====

Vamos começar examinando _builderlib.py_, dividido em duas partes: o <<builderlib_top_ex>> e o <<builderlib_bottom_ex>>.

[[builderlib_top_ex]]
.builderlib.py: primeira parte do módulo
====
[source, python]
----
include::../code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_TOP]
----
====
<1> Essa é uma fábrica de classes para implementar...
<2> ...um método `+__init_subclass__+`.
<3> Define uma função para ser adicionada à subclasse na atribuição abaixo.
<4> Um decorador de classes.
<5> Função a ser adicionada à classe decorada.
<6> Devolve a classe recebida como argumento.

Continuando _builderlib.py_ no <<builderlib_bottom_ex>>...

[[builderlib_bottom_ex]]
.builderlib.py: a parte final do módulo
====
[source, python]
----
include::../code/24-class-metaprog/evaltime/builderlib.py[tags=BUILDERLIB_BOTTOM]
----
====
<1> Uma classe descritora para demonstrar quando...
<2> ...uma instância do descritor é criada, e quando...
<3> ...`+__set_name__+` será invocado durante a criação da classe `owner`.
<4> Como os outros métodos, este `+__set__+` não faz nada, exceto exibir seus argumentos.

Se importarmos _builderlib.py_ no console de Python, veremos o seguinte:

[source, python]
----
>>> import builderlib
@ builderlib module start
@ Builder body
@ Descriptor body
@ builderlib module end
----

Note que as linhas exibidas por _builderlib.py_ têm uma `@` à esquerda.

Agora voltamos a atenção para _evaldemo.py_, que acionará métodos especiais em
_builderlib.py_ (no <<evaldemo_ex>>).

[[evaldemo_ex]]
.evaldemo.py: script para experimentar com _builderlib.py_
====
[source, python]
----
include::../code/24-class-metaprog/evaltime/evaldemo.py[]
----
====
<1> Aplica um decorador.
<2> Cria uma subclasse de `Builder` para acionar seu `+__init_subclass__+`.
<3> Instancia o descritor.
<4> Isso só será chamado se o módulo for executado como o programa principal.

As chamadas a `print` em _evaldemo.py_ têm um `#` como prefixo.
Se você abrir o console novamente e importar _evaldemo.py_, a saída aparece no <<evaldemo_console_ex>>.

[[evaldemo_console_ex]]
.Experimentos de console com _evaldemo.py_
====
[source, python]
----
>>> import evaldemo
@ builderlib module start  <1>
@ Builder body
@ Descriptor body
@ builderlib module end
# evaldemo module start
# Klass body  <2>
@ Descriptor.__init__(<Descriptor instance>)  <3>
@ Descriptor.__set_name__(<Descriptor instance>,
      <class 'evaldemo.Klass'>, 'attr')                <4>
@ Builder.__init_subclass__(<class 'evaldemo.Klass'>)  <5>
@ deco(<class 'evaldemo.Klass'>)  <6>
# evaldemo module end
----
====
<1> As primeiras quatro linhas são o resultado de `from builderlib import …` +
Elas não aparecerão se você usar a mesma sessão do console do experimento anterior, pois _builderlib.py_ já estará importado.
<2> Isto sinaliza que Python começou a ler o corpo de `Klass`. Neste momento o objeto classe ainda não existe.
<3> A instância do descritor é criada e vinculada a `attr`, no espaço de nomes que Python passará para o construtor default do objeto classe: `+type.__new__+`.
<4> Neste ponto, a função embutida de Python `+type.__new__+` já criou o objeto `Klass` e invoca
`+__set_name__+` em cada instância das classes do descritor que oferecem aquele método, passando `Klass` como argumento `owner`.
<5> `+type.__new__+` então chama `+__init_subclass__+` na superclasse de `Klass`, passando `Klass` como único argumento.
<6> Quando `+type.__new__+` devolve o objeto classe, Python aplica o decorador. Neste exemplo, a classe devolvida por `deco` está vinculada a `Klass` no espaço de nomes do módulo

A implementação de `+type.__new__+` está escrita em C.
O comportamento que acabei de descrever está documentado na seção
https://fpy.li/bx[«Criando o objeto classe»], no capítulo
_Modelo de Dados_ da referência de Python.

Note que a função `main()` de _evaldemo.py_ (<<evaldemo_ex>>)
rodou durante a sessão no console (<<evaldemo_console_ex>>), portanto nenhuma instância de `Klass` foi criada.
Todas as ações que vimos foram acionadas por operações no momento da importação:
importar `builderlib` e definir `Klass`.

Se você executar _evaldemo.py_ como um script, verá a mesma saída do <<evaldemo_console_ex>>,
com mais linhas logo antes do final.
As linhas adicionais são o resultado da execução de `main()` no <<evaldemo_script_ex>>:

[[evaldemo_script_ex]]
.Executando _evaldemo.py_ como um programa
====
[source]
----
$ ./evaldemo.py
[... 9 linhas omitidas ...]
@ deco(<class '__main__.Klass'>)  <1>
@ Builder.__init__(<Klass instance>)  <2>
# Klass.__init__(<Klass instance>)
@ SuperA.__init_subclass__:inner_0(<Klass instance>)  <3>
@ deco:inner_1(<Klass instance>)  <4>
@ Descriptor.__set__(<Descriptor instance>, <Klass instance>, 999)  <5>
# evaldemo module end
----
====
<1> As 10 primeiras linhas—incluindo essa—são as mesma que aparecem no <<evaldemo_console_ex>>.
<2> Saída de `+super().__init__()+` em `+Klass.__init__+`.
<3> Saída de `obj.method_a()` em `main`; o `method_a` foi injetado por
`+SuperA.__init_subclass__+`.
<4> Saída de `obj.method_b()` em `main`; `method_b` foi injetado por `deco`.
<5> Saída de `obj.attr = 999` em `main`.

Uma classe base com `+__init_subclass__+` ou um decorador de classes são ferramentas poderosas, mas elas estão limitadas a trabalhar sobre uma classe já criada por `+type.__new__+` por baixo dos panos.
Nas raras ocasiões em que for preciso ajustar os argumentos passados a `+type.__new__+`, uma metaclasse é necessária.
Esse é o destino final deste capítulo—e deste livro!((("", startref="CMimport24")))


[[metclass101_sec]]
=== Introdução às metaclasses


[quote, Tim Peters, inventor do algoritmo timsort e prolífico mantenedor de Python]
____

&#x5b;Metaclasses&#x5d; são uma mágica tão profunda que 99% dos usuários jamais
deveriam se preocupar com elas. Quem se pergunta se precisa delas, não precisa
(quem realmente precisa de metaclasses sabe com certeza, e não precisa que
lhe expliquem a razão).footnote:[Mensagem a comp.lang.python, assunto:
https://fpy.li/24-12[_Acrimony in c.l.p._] (animosidade na c.l.p., o grupo _comp.lang.python_).
Esta é
outra parte da mesma mensagem de 23 de dezembro de 2002, que citei no _Prefácio_ de _Python Fluente_.
O TimBot estava inspirado naquele dia.]

____



Uma((("metaclasses", "basics of", id="MCbasics24")))((("class metaprogramming", "metaclass basics", id="CMmetabasic24"))) metaclasse é uma fábrica de classes.
Diferente de `record_factory`, do <<record_factory_ex>>,
uma metaclasse é escrita como uma classe.
Em outras palavras, uma metaclasse é uma classe cujas instâncias são classes.
A <<meta_class_and_class_mgn>> usa a _Notação de Engenhocas e Bugigangas_ para ilustrar uma metaclasse:
uma engenhoca que produz outra engenhoca.

[[meta_class_and_class_mgn]]
.Uma metaclasse é uma classe que cria classes.
image::../images/flpy_2401.png[align="center", pdfwidth=10cm]

Pense no modelo de objetos de Python: classes são objetos, portanto cada classe deve ser uma instância de alguma outra classe.
Por default, as classes de Python são instâncias de `type`.
Em outras palavras, `type` é a metaclasse da maioria das classes, sejam elas embutidas ou definidas pelo usuário:

[source, python]
----
>>> str.__class__
<class 'type'>
>>> from bulkfood_v5 import LineItem
>>> LineItem.__class__
<class 'type'>
>>> type.__class__
<class 'type'>
----

Para evitar regressões infinitas, a classe de `type` é `type`, como mostra a última linha.

Observe que não estou dizendo que `str` ou `LineItem` são subclasses de `type`. Estou dizendo que `str` e `LineItem` são instâncias de `type`.
Elas são todas subclasses de `object`. A <<class_hier_2tops_uml>> pode ajudar você a contemplar essa estranha realidade.

[[class_hier_2tops_uml]]
.Os dois diagramas são verdadeiros. O da esquerda enfatiza que `str`, `type`, e `LineItem` são subclasses de `object`. O da direita ressalta que `str`, `object`, e `LineItem` são instâncias de `type`, pois todas são classes.
image::../images/flpy_2402.png[align="center", pdfwidth=10cm]

[NOTE]
====

As classes `object` e `type` têm uma relação singular: `object` é uma instância
de `type`, e `type` é uma subclasse de `object`. Esta relação é "mágica": ela
não pode ser expressa em Python, porque cada uma das classes teria que existir
antes da outra poder ser definida. O fato de `type` ser uma instância de si
mesma também é mágico.

====

O próximo trecho mostra que a classe de `collections.Iterable` é `abc.ABCMeta`.
Observe que `Iterable` é uma classe abstrata, mas `ABCMeta` é uma classe concreta--afinal, `Iterable` é uma instância de `ABCMeta`:

[source, python]
----
>>> from collections.abc import Iterable
>>> Iterable.__class__
<class 'abc.ABCMeta'>
>>> import abc
>>> from abc import ABCMeta
>>> ABCMeta.__class__
<class 'type'>
----

Por fim, a classe de `ABCMeta` também é `type`.
Toda classe é uma instância de `type`, direta ou indiretamente, mas só metaclasses são também subclasses de `type`.
Esta é a relação mais importante para entender as metaclasses:
uma metaclasse, tal como `ABCMeta`, herda de `type` o poder de criar classes.
A <<metaclass_abcmeta_uml>> ilustra essa relação fundamental.

[[metaclass_abcmeta_uml]]
.`Iterable` é uma subclasse de `object` e uma instância de `ABCMeta`. Tanto `object` quanto `ABCMeta` são instâncias de `type`, mas a relação crucial aqui é que `ABCMeta` também é uma subclasse de `type`, porque `ABCMeta` é uma metaclasse. Neste diagrama, `Iterable` é a única classe abstrata.
image::../images/flpy_2403.png[align="center", pdfwidth=6cm]

A lição importante aqui é que metaclasses são subclasses de `type`, e é isso que permite a elas funcionarem como fábricas de classes.
Uma metaclasse pode customizar suas instâncias implementando métodos especiais, como demonstram as próximas seções.((("", startref="MCbasics24")))

[[how_metaclass_customizes]]
==== Como uma metaclasse customiza uma classe

Para((("metaclasses", "customizing classes"))) usar uma metaclasse, é crucial entender como
`+__new__+` funciona em qualquer classe.
Isto foi discutido na <<flexible_new_sec>>.

A mesma mecânica se repete no nível "meta", quando uma metaclasse está prestes a criar uma nova instância, que é uma classe.
Considere a declaração abaixo:

[source, python]
----
class Klass(SuperKlass, metaclass=MetaKlass):
    x = 42
    def __init__(self, y):
        self.y = y
----

Para processar o bloco `class` acima, o interpretador invoca `+MetaKlass.__new__+`,
cuja implementação herdada da classe `type` recebe os seguintes argumentos:

<<<

`meta_cls`:: A própria metaclasse, porque `+__new__+` funciona como um método de classe. +
Ex: `MetaKlass`

`cls_name`:: O nome da classe a ser criada, como uma string. Ex: `'Klass'`

`bases`:: Uma tupla com as superclasses da classe a ser criada. Ex: `(SuperKlass,)`

`cls_dict`:: Os métodos e outros atributos da classe a ser criada. Ex:
+
--
[source, python]
----
{'x': 42, '__init__': <function initializer at 0x1009c4040>}
----
--

Ao implementar `+MetaKlass.__new__+`, podemos inspecionar e modificar aqueles
argumentos antes de passá-los para `+super().__new__+`, que por fim invocará
`+type.__new__+` para criar o novo objeto classe.
Após `+super().__new__+` retornar, podemos aplicar processamentos
adicionais à classe recém-criada, antes de devolvê-la para o Python. 

Depois de invocar `+MetaKlass.__new__+` e receber a nova `Klass`,
Python então invoca `+SuperKlass.__init_subclass__+`,
passando a classe que criamos. 
Se existir um decorador de classe acima do bloco `class`, ele é aplicado
depois que `+__init_subclass__+` retorna.
Finalmente, Python
vincula o objeto classe a seu nome no espaço de nomes atual.
No caso mais comum, a instrução `class` está no primeiro nível do módulo,
e a `Klass` é inserida no espaço de nomes global do módulo.

O processamento mais comum realizado no `+__new__+` de uma metaclasse é
adicionar ou substituir itens no `cls_dict`, que representa o espaço
de nomes da classe em construção.
Por exemplo, podemos injetar métodos na classe em construção adicionando
funções a `cls_dict`. Entretanto, observe que adicionar métodos pode também ser
feito após a classe ser criada, e é por essa razão que podemos fazer isso usando
`+__init_subclass__+` ou um decorador de classe.

<<<
Um atributo que precisa ser adicionado a `cls_dict` antes de se executar `+type.__new__+` é
`+__slots__+`, como discutido na <<why_cannot_config_slots_sec>>.
O método `+__new__+` de uma metaclasse é o lugar ideal para configurar `+__slots__+`.
A próxima seção mostra como fazer isso.


[[nice_metaclass_sec]]
==== Um belo exemplo de metaclasse

A((("metaclasses", "example metaclass", id="MCexample24")))
metaclasse `MetaBunch` apresentada aqui é uma variação do último exemplo no
Capítulo 4 do _Python in a Nutshell, 3rd ed._, de Alex
Martelli, Anna Ravenscroft, e Steve Holden, escrito para rodar sob Python 2.7 e
3.5.footnote:[Os autores gentilmente me deram permissão para usar seu exemplo.
`MetaBunch` apareceu pela primeira vez em uma mensagem enviada por Martelli para
o grupo comp.lang.python, em 7 de julho de 2002, com o assunto
https://fpy.li/24-13["a nice metaclass example (was Re: structs in python)" (_um
belo exemplo de metaclasse (era Re: structs no python)_)], na sequência de uma
discussão sobre estruturas de dados similares a registros no Python. O código
original de Martelli, para Python 2.2, ainda roda após uma única modificação:
para usar uma metaclasse no Python 3, é necessário usar o argumento nomeado
`metaclass` na declaração da classe (por exemplo, `Bunch(metaclass=MetaBunch)`),
em vez da convenção antiga, que era adicionar um atributo `+__metaclass__+` no
corpo da classe.] Assumindo o uso de Python 3.6 ou mais recente, pude
simplificar ainda mais o código.

Mas primeiro vamos ver o que a classe base `Bunch` oferece:

[source, python]
----
include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_1]
----

Subclasses de `Bunch` usam atributos de classe com valores atribuídos, que então se tornam os valores default dos atributos de instância.
O `+__repr__+` gerado omite os argumentos cujos valores são iguais aos defaults.

`MetaBunch`—a metaclasse de `Bunch`—gera `+__slots__+` para a nova classe a
partir de atributos de classe declarados na classe do usuário.
Isto impede que atributos com nomes não declarados sejam
criados na instanciação ou por atribuição posterior.

<<<

[source, python]
----
include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=BUNCH_POINT_DEMO_2]
----

Vamos agora mergulhar no elegante código de `MetaBunch`, no <<metabunch_ex>>.

[[metabunch_ex]]
.metabunch/from3.6/bunch.py: a metaclasse `MetaBunch` e a classe `Bunch`
====
[source, python]
----
include::../code/24-class-metaprog/metabunch/from3.6/bunch.py[tags=METABUNCH]
----
====

<1> Para criar uma nova metaclasse, herdamos de `type`.

<2> `+__new__+` funciona como um método de classe, mas a classe é uma
metaclasse, então prefiro nomear o primeiro argumento `meta_cls` (`mcs` é uma
alternativa comum). Os três argumentos seguintes são os mesmos da assinatura de
três parâmetros de `type()`, quando invocada diretamente para criar uma classe.

<3> `defaults` vai preservar um mapeamento de nomes de atributos e seus valores
default.

<4> Isto será injetado na nova classe.

<5> Lê `defaults` e define o atributo de instância correspondente, com o valor
extraído de `kwargs`, ou um valor default.

<6> Se ainda houver itens em `kwargs`, isso significa que não há posição
restante onde possamos colocá-los. Adotamos a prática de _falhar rápido_,
então não queremos ignorar silenciosamente os itens em excesso.

<7> `+__repr__+` devolve uma string que se parece com uma chamada ao
construtor—por exemplo, `Point(x=3)`, exibindo somente os atributos
com valores diferentes dos respectivos valores default.

<8> Inicializa o espaço de nomes para a nova classe.

<9> Itera sobre o espaço de nomes da classe fornecida pelo usuário.

<10> Se um `name` no formato _dunder_ é encontrado,
copia o item para o espaço de nomes da nova classe, a menos que ele já esteja
lá. Isto evita que usuários sobrescrevam `+__init__+`, `+__repr__+` e outros
atributos definidos pelo Python, como `+__qualname__+` e `+__module__+`.

<11> Se `name` não for um _dunder_, acrescenta `name` a `+__slots__+` e armazena
seu `value` em `defaults`.

<12> Cria e devolve a nova classe.

<13> Fornece uma classe base, assim os usuários não precisam ver `MetaBunch`.

`MetaBunch` funciona porque tem a oportunidade de configurar `+__slots__+` antes
de invocar `+super().__new__+` para criar a classe final. Como sempre em
metaprogramação, o fundamental é entender a sequência de ações. Vamos fazer
outro experimento sobre as etapas de avaliação, agora com uma metaclasse.((("",
startref="MCexample24")))


==== Experimento com as etapas de avaliação de metaclasses

Esta((("metaclasses", "metaclass evaluation time experiment", id="MCtime24")))
é uma variação da <<evaldemo_sec>>, acrescentando uma metaclasse para ter mais emoção.
O módulo _builderlib.py_ é o mesmo de antes, mas o script principal agora é _evaldemo_meta.py_,
listado no <<evaldemo_meta_ex>>.

[[evaldemo_meta_ex]]
.evaldemo_meta.py: experimentando com uma metaclasse
====
[source, python]
----
include::../code/24-class-metaprog/evaltime/evaldemo_meta.py[]
----
====
<1> Importa `MetaKlass` de _metalib.py_, que veremos no <<metalib_bottom_ex>>.
<2> Declara `Klass` como uma subclasse de `Builder` e uma instância de `MetaKlass`.
<3> Este método é injetado por `+MetaKlass.__new__+`, como veremos adiante.


[WARNING]
====
Em nome da ciência, o <<evaldemo_meta_ex>> desafia qualquer razão prática e aplica três técnicas diferentes de metaprogramação em `Klass`:
um decorador, uma classe base usando `+__init_subclass__+`, e uma metaclasse customizada.
Se você fizer isto em código de produção, não me culpe.
Aqui o objetivo é observar a ordem na qual as três técnicas interferem no processo de criação de uma classe.
====

Como no experimento anterior com as etapas de avaliação, este exemplo não faz nada, apenas exibe mensagens revelando o fluxo de execução.
O <<metalib_top_ex>> mostra a primeira parte do código de _metalib.py_—o restante está no <<metalib_bottom_ex>>.

<<<

[[metalib_top_ex]]
.metalib.py: a classe `NosyDict`
====
[source, python]
----
include::../code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_TOP]
----
====

Escrevi a classe `NosyDict` para  sobrescrever `+__setitem__+` e exibir cada `key` e cada `value` conforme eles são definidos.
A metaclasse vai usar uma instância de `NosyDict` para guardar o espaço de nomes da classe em construção, revelando um pouco mais sobre o funcionamento interno do Python.

A principal atração de _metalib.py_ é a metaclasse no <<metalib_bottom_ex>>.
Ela implementa o método especial `+__prepare__+`, um método de classe que Python só invoca em metaclasses.
O método `+__prepare__+` oferece a primeira oportunidade para influenciar o processo de criação de uma nova classe.

[TIP]
====
Ao programar uma metaclasse, acho útil adotar a seguinte convenção de nomenclatura para argumentos de métodos especiais:

* Usar `cls` em vez de `self` para métodos de instância, pois a instância é uma classe.

* Usar `meta_cls` em vez de `cls` para métodos de classe, pois a classe é uma metaclasse.
Lembre-se de que `+__new__+` se comporta como um método de classe mesmo sem o decorador `@classmethod`.
====

<<<

[[metalib_bottom_ex]]
.metalib.py: a `MetaKlass`
====
[source, python]
----
include::../code/24-class-metaprog/evaltime/metalib.py[tags=METALIB_BOTTOM]
----
====
<1> `+__prepare__+` deve ser declarado como um método de classe.
Ele não é um método de instância, pois a classe em construção ainda não existe quando Python invoca `+__prepare__+`.
<2> Python invoca `+__prepare__+` em uma metaclasse para obter um mapeamento para guardar o espaço de nomes da classe em construção.
<3> Devolve uma instância de `NosyDict` para ser usada como o espaço de nomes.
<4> `cls_dict` é uma instância de `NosyDict` devolvida por `+__prepare__+`.
<5> `+type.__new__+` exige um `dict` de verdade como último argumento (não um mapeamento qualquer), então fornecemos o atributo `data` de `NosyDict`, herdado de `UserDict`.
<6> Injeta um método na classe recém-criada.
<7> Como sempre, `+__new__+` devolve o objeto que acaba de ser criado—neste caso, a nova classe.
<8> Definir `+__repr__+` em uma metaclasse permite customizar o `repr()` de objetos classe.

O principal caso de uso para `+__prepare__+` antes de Python 3.6 era fornecer um
`OrderedDict` para preservar os atributos de uma classe em construção,
para que o `+__new__+` da metaclasse pudesse processar aqueles atributos na ordem
em que aparecem no código-fonte da definição de classe do usuário.
Agora que `dict` preserva a ordem de inserção, `+__prepare__+` raramente é necessário.
Veremos um uso criativo para ele na <<metahack_sec>>.

Importar _metalib.py_ no console de Python não é muito empolgante.
Observe o uso de `%` para prefixar as linhas geradas por esse módulo:

[source, python]
----
>>> import metalib
% metalib module start
% MetaKlass body
% metalib module end
----

Muitas coisas acontecem quando importamos _evaldemo_meta.py_, como visto no  <<evaldemo_meta_console_ex>>.

[[evaldemo_meta_console_ex]]
.Experimento com _evaldemo_meta.py_ no console
====
[source, python]
----
>>> import evaldemo_meta
@ builderlib module start
@ Builder body
@ Descriptor body
@ builderlib module end
% metalib module start
% MetaKlass body
% metalib module end
# evaldemo_meta module start  <1>
% MetaKlass.__prepare__(<class 'metalib.MetaKlass'>, 'Klass',  <2>
                        (<class 'builderlib.Builder'>,))
% NosyDict.__setitem__(<NosyDict instance>, '__module__',
  'evaldemo_meta')  <3>
% NosyDict.__setitem__(<NosyDict instance>, '__qualname__', 'Klass')
# Klass body
@ Descriptor.__init__(<Descriptor instance>)  <4>
% NosyDict.__setitem__(<NosyDict instance>, 'attr',
  <Descriptor instance>)  <5>
% NosyDict.__setitem__(<NosyDict instance>, '__init__',
                       <function Klass.__init__ at …>)  <6>
% NosyDict.__setitem__(<NosyDict instance>, '__repr__',
                       <function Klass.__repr__ at …>)
% NosyDict.__setitem__(<NosyDict instance>, '__classcell__', <cell at …: empty>)
% MetaKlass.__new__(<class 'metalib.MetaKlass'>, 'Klass',
                    (<class 'builderlib.Builder'>,),
                    <NosyDict instance>)  <7>
@ Descriptor.__set_name__(<Descriptor instance>,
                          <class 'Klass' built by MetaKlass>, 'attr')  <8>
@ Builder.__init_subclass__(<class 'Klass' built by MetaKlass>)
@ deco(<class 'Klass' built by MetaKlass>)
# evaldemo_meta module end
----
====
<1> As linhas acima desta são exibidas durante a importação de _builderlib.py_ e _metalib.py_.
<2> Python invoca `+__prepare__+` para iniciar o processamento de uma instrução `class`.
<3> Antes de analisar o corpo da classe, Python acrescenta `+__module__+` e `+__qualname__+` ao espaço de nomes de uma classe em construção.
<4> A instância do descritor é criada...
<5> ...e vinculada a `attr` no espaço de nomes da classe.
<6> Os métodos `+__init__+` e `+__repr__+` são definidos e adicionados ao espaço de nomes.
<7> Após terminar o processamento do corpo da classe, Python chama `+MetaKlass.__new__+`.
<8> Após o método `+__new__+` da metaclasse devolver a classe recém-criada,
os métodos `+__set_name__+`, `+__init_subclass__+` e o decorador `@deco` são invocados nesta ordem, 

<<<
Se executarmos _evaldemo_meta.py_ como um script, `main()` é invocada, e mais coisas acontecem. Veja o <<evaldemo_meta_script_ex>>.

[[evaldemo_meta_script_ex]]
.Rodando _evaldemo_meta.py_ como um programa
====
[source]
----
$ ./evaldemo_meta.py
[... 20 linhas omitidas ...]
@ deco(<class 'Klass' built by MetaKlass>)  <1>
@ Builder.__init__(<Klass instance>)
# Klass.__init__(<Klass instance>)
@ SuperA.__init_subclass__:inner_0(<Klass instance>)
@ deco:inner_1(<Klass instance>)
% MetaKlass.__new__:inner_2(<Klass instance>)  <2>
@ Descriptor.__set__(<Descriptor instance>, <Klass instance>, 999)
# evaldemo_meta module end
----
====
<1> As primeiras 21 linhas—incluindo esta—são as mesmas que aparecem no <<evaldemo_meta_console_ex>>.
<2> Acionado por `obj.method_c()` em `main`; `method_c` foi injetado por `+MetaKlass.__new__+`.

Vamos agora voltar à ideia da classe `Checked`, com descritores `Field`
implementando validação de tipo durante a execução, e ver como aquilo pode ser
feito com uma metaclasse.((("", startref="CMmetabasic24")))((("",
startref="MCtime24")))


=== `Checked`, agora com metaclasse

Não((("class metaprogramming", "metaclass solution for checkedlib.py",
id="CMcheck24")))((("metaclasses", "metaclass solution for checkedlib.py",
id="MCchecked24"))) quero estimular a otimização prematura nem excessos de engenharia (_over),
então aqui temos um cenário de ficção para justificar reescrever _checkedlib.py_ com `+__slots__+`,
exigindo a aplicação de uma metaclasse.
Fique à vontade para pular a historinha.

<<<
.Senta que lá vem história
****
Nosso _checkedlib.py_ usando `+__init_subclass__+` é um sucesso na empresa, e em qualquer dado momento nossos servidores de produção têm milhões de instâncias de subclasses de `Checked` em suas memórias.

Analisando (_profiling_) o uso de memória durante a execução, constatamos que usar `+__slots__+` pode reduzir os custos de hospedagem, por duas razões:

* Menos uso de memória, já que as instâncias de `Checked` não precisarão ter seus próprios
`+__dict__+`
* Melhor desempenho, pela remoção de `+__setattr__+`, que foi criado só para bloquear atributos indesejados,
mas é acionado na instanciação e para todas as definições de atributos antes de
`+Field.__set__+` ser invocado para fazer seu trabalho

****


O módulo _metaclass/checkedlib.py_, que estudaremos a seguir, é um substituto
direto para _initsub/checkedlib.py_. Os doctests contidos nos dois módulos são
idênticos, bem como os arquivos _checkedlib_test.py_ para o _pytest_.

A complexidade de _checkedlib.py_ é ocultada do usuário.
Aqui está o código-fonte de um script que usa o pacote:

[[checked_demo_ex]]
.metaclass/checked_demo.py: exemplo de uso da classe `Checked`
====
[source, python]
----
include::../code/24-class-metaprog/checked/metaclass/checked_demo.py[tags=MOVIE_DEMO]
----
====

<<<
Esta definição concisa da classe `Movie` se vale de três instâncias do descritor de validação `Field`, uma configuração de `+__slots__+`, cinco métodos herdados de `Checked` e uma metaclasse para juntar tudo isso.
A única parte visível de `checkedlib` é a classe base `Checked`.

Estude a <<checkedlib_uml_mgn>>.
A Notação Engenhocas e Bugigangas((("UML class diagrams", "annotated with MGN"))) complementa o diagrama de classes UML, tornando mais visível a relação entre classes e instâncias.

[[checkedlib_uml_mgn]]
.Diagrama de classes UML com MGN: a meta-engenhoca `CheckedMeta` cria a engenhoca `Movie`. A engenhoca `Field` cria os descritores `title`, `year`, e `box_office`, que são atributos de classe de `Movie`. Os dados dos campos são armazenados nos atributos `+_title+`, `+_year+` e `+_box_office+` em cada instância de `Movie`. Note a fronteira do pacote `checkedlib`. O desenvolvedor de `Movie` não precisa entender todo o maquinário dentro de _checkedlib.py_.
image::../images/flpy_2404.png[Diagrama de classes UML+MGN para `CheckedMeta`&#x2c; `Movie` etc.]


Por exemplo, uma classe `Movie` usando a nova _checkedlib.py_ é uma instância de `CheckedMeta` e uma subclasse de `Checked`.
Os atributos de classe `title`, `year` e `box_office` de `Movie` são três instâncias diferentes de `Field`.
Cada instância de `Movie` tem seus próprios atributos `_title`, `_year` e `_box_office`, para armazenar os valores dos campos correspondentes.

Vamos agora estudar o código, começando pela classe `Field` do <<checked_field_meta_ex>>.
A classe descritora `Field` está um pouco diferente. Nos exemplos anteriores, cada instância do descritor `Field` armazenava seu valor na instância gerenciada, usando um atributo de mesmo nome. Por exemplo, na classe `Movie`, o descritor `title` armazenava o valor do campo em um atributo `title` na instância gerenciada.
Isso tornava desnecessário que `Field` implementasse um método `+__get__+`.

Entretanto, quando uma classe como `Movie` usa `+__slots__+`, ela não pode ter atributos de classe e atributos de instância com o mesmo nome. Cada instância do descritor é um atributo de classe, e agora precisamos de atributos de armazenamento separados em cada instância. O código usa o nome do descritor prefixado por um único `_`.
Portanto, instâncias de `Field` têm atributos `name` e `storage_name` distintos, e implementamos
`+Field.__get__+`.

O <<checked_field_meta_ex>> mostra o código-fonte de `Field`, com os textos explicativos descrevendo apenas as mudanças nessa versão.

[[checked_field_meta_ex]]
.metaclass/checkedlib.py: o descritor `Field` com `storage_name` e `+__get__+`
====
[source, python]
----
include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_FIELD]
----
====
<1> Define `storage_name` a partir do argumento `name`.
<2> Se `+__get__+` recebe `None` como argumento `instance`, o descritor está sendo lido desde a própria classe gerenciada, não de uma instância gerenciada. Neste caso devolvemos o descritor.
<3> Caso contrário, devolve o valor armazenado no atributo chamado `storage_name`.
<4> `+__set__+` agora usa `setattr` para definir ou atualizar o atributo gerenciado.

O <<checked_metaclass_ex>> mostra o código da metaclasse que controla este exemplo.

[[checked_metaclass_ex]]
.metaclass/checkedlib.py: a metaclasse `CheckedMeta`
====
[source, python]
----
include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_META]
----
====
<1> `+__new__+` é o único método implementado em `CheckedMeta`.
<2> Só altera a classe se seu `cls_dict` não incluir `+__slots__+`. Se `+__slots__+` já existe, assumimos que esta é a classe base `Checked` e não uma subclasse definida pelo usuário, e cria a classe sem modificações.
<3> Nos exemplos anteriores usamos `typing.get_type_hints` para obter as dicas de tipo, mas aquilo exige uma classe existente como primeiro argumento. Neste ponto, a classe que estamos configurando ainda não existe, então precisamos recuperar `+__annotations__+` diretamente do `cls_dict`—o espaço de nomes da classe em construção, que Python passa como último argumento para o `+__new__+` da metaclasse.
<4> Itera sobre `type_hints` para...
<5> ...criar um `Field` para cada atributo anotado...
<6> ...sobrescreve o item correspondente em `cls_dict` com a instância de `Field`...
<7> ...e acrescenta o `storage_name` do campo à lista que usaremos para...
<8> ...preencher o `+__slots__+` no `cls_dict`—o espaço de nomes da classe em construção.
<9> Por fim, invocamos `+super().__new__+`.

A última parte de _metaclass/checkedlib.py_ é a classe `Checked`. Usuários da _checkedlib_ criam subclasses de `Checked`, como `Movie` no <<checked_demo_ex>>.

O código desta versão de `Checked` é o mesmo da `Checked` em _initsub/checkedlib.py_
(listada no <<ex_checked_class_top>> e no <<checked_class_bottom_ex>>), com três modificações:

. O acréscimo de um `+__slots__+` vazio, para sinalizar a `+CheckedMeta.__new__+` que esta classe não precisa de processamento especial.
. A remoção de `+__init_subclass__+`, cujo trabalho agora é feito por `+CheckedMeta.__new__+`.
. A remoção de `+__setattr__+`, pois a definição de `+__slots__+` na classe definida pelo usuário já impede a criação de atributos não declarados.

O <<checked_baseclass_ex>> é a listagem completa da versão final de `Checked`.

[[checked_baseclass_ex]]
.metaclass/checkedlib.py: a classe base `Checked`
====
[source, python]
----
include::../code/24-class-metaprog/checked/metaclass/checkedlib.py[tags=CHECKED_CLASS]
----
====

Isto conclui nossa terceira versão de uma fábrica de classes com descritores validados.
A próxima seção trata de algumas questões gerais relacionadas a metaclasses.((("", startref="MCchecked24")))((("", startref="CMcheck24")))

[[metaclases_real_world_sec]]
=== Metaclasses no mundo real

Metaclasses((("metaclasses", "considerations for use",
id="MCconsider24")))((("class metaprogramming", "metaclass issues",
id="CMissue24"))) são poderosas mas complexas. Antes de se decidir a implementar
uma metaclasse, considere os pontos a seguir.


[[metaclass_modern_features_sec]]
==== Recursos modernos simplificam ou substituem as metaclasses

Ao longo do tempo, vários casos de uso comum de metaclasses se tornaram redundantes devido a novos recursos da linguagem:

Decoradores de classes:: Mais simples de entender que metaclasses, e com menor probabilidade de causar conflitos com classes base e metaclasses.

`+__set_name__+`:: Elimina a necessidade de uma metaclasse com lógica customizada para definir automaticamente o nome de um descritor.footnote:[Na primeira edição de _Python Fluente_, as versões mais avançadas da classe `LineItem` usavam uma metaclasse apenas para definir o nome do armazenamento dos atributos. Veja o código nas metaclasses do https://fpy.li/24-14[exemplo da comida a granel], no repositório de código da primeira edição.]

`+__init_subclass__+`:: Fornece uma forma de customizar a criação de classes que é transparente para o usuário final e ainda mais simples que um decorador—mas pode introduzir conflitos em uma hierarquia de classes complexa.

O `dict` embutido preservando a ordem de inserção de chaves:: Eliminou((("keys", "preserving key insertion order"))) a principal razão para usar `+__prepare__+`, que era
fornecer um `OrderedDict` para armazenar o espaço de nomes de uma classe em construção.
Python só invoca `+__prepare__+` em metaclasses e então, se fosse necessário processar o espaço de nomes da classe na ordem em que eles aparecem no código-fonte, antes de Python 3.6 era preciso usar uma metaclasse.

Em 2021, todas as versões sob manutenção ativa do CPython suportam todos os recursos listados acima.

Sigo defendendo esses recursos porque vejo muita complexidade desnecessária em nossa profissão, e as metaclasses são uma porta de entrada para a complexidade.


==== Metaclasses são um recurso estável da linguagem

As metaclasses foram introduzidas no Python em 2002, junto com as assim chamadas
_new-style classes_ (classes com novo estilo), descritores e propriedades.

É impressionante que o exemplo do `MetaBunch`, postado pela primeira vez por
Alex Martelli em julho de 2002, ainda funcione no Python 3.9—a única modificação
é a forma de especificar a metaclasse a ser usada, que no Python 3 fazemos com esta sintaxe:

[source, python]
----
class Bunch(metaclass=MetaBunch):
    ...
----

<<<
Nenhum dos recursos modernos citados na <<metaclass_modern_features_sec>> quebrou
código existente que usava metaclasses. Mas um código legado com metaclasses
frequentemente pode ser simplificado através do uso daqueles recursos,
especialmente ignorando versões de Python anteriores à 3.6—que hoje são obsoletas.


==== Uma classe só pode ter uma metaclasse

Se sua declaração de classe envolver duas ou mais metaclasses, você verá essa intrigante mensagem de erro:

[source]
----
TypeError: metaclass conflict: the metaclass of a derived class
must be a (non-strict) subclass of the metaclasses of all its bases
(conflito de metaclasses: a metaclasse de uma classe derivada deve
ser uma subclasse (não-estrita) das metaclasses de todas as suas bases)
----

Isso pode acontecer mesmo sem herança múltipla.
Por exemplo, a declaração abaixo pode levantar aquele `TypeError`:

[source, python]
----
class Record(abc.ABC, metaclass=PersistentMeta):
    pass
----

Vimos que `abc.ABC` é uma instância da metaclasse `abc.ABCMeta`.
Se aquela metaclasse `Persistent` não for uma subclasse de `abc.ABCMeta`,
você tem um conflito de metaclasses.

Há duas maneiras de lidar com esse erro:

* Encontre outra forma de fazer o que precisa ser feito, evitando o uso de pelo menos uma das metaclasses envolvidas.
* Escreva a sua própria metaclasse `PersistentABCMeta` como uma subclasse tanto de `abc.ABCMeta` quanto de `PersistentMeta`, usando herança múltipla, e faça dela a única metaclasse de `Record`.footnote:[Se você sentiu vertigem ao ponderar sobre as implicações de herança múltipla com metaclasses, bom para você. Eu também passaria longe dessa solução.]

<<<
[TIP]
====

A solução de uma metaclasse com duas metaclasses base pode ser implementada para atender um prazo em caso de desespero.
Na minha experiência, a programação de metaclasses sempre leva mais tempo que o esperado,
tornando esta abordagem arriscada diante de um prazo inflexível.
Se fizer isso e cumprir o prazo previsto, seu código pode conter bugs sutis.
E mesmo na ausência de bugs conhecidos, esta abordagem deveria ser considerada uma dívida técnica,
pelo simples fato de ser difícil de entender e manter.

====


==== Metaclasses devem ser detalhes de implementação

Além de `type`, existem apenas outras seis metaclasses em toda a biblioteca padrão de Python 3.9.
As metaclasses mais utilizadas (indiretamente) provavelmnete são `abc.ABCMeta`, `typing.NamedTupleMeta` e
`enum.EnumMeta`.
Nenhuma delas deve aparecer explicitamente no código da aplicação, mas somente em bibliotecas.
Podemos considerá-las detalhes de implementação.

Apesar de ser possível fazer metaprogramações muito loucas com metaclasses, é melhor se ater ao
https://fpy.li/24-15[princípio do menor espanto], de forma que a maioria dos usuários possa de fato considerar metaclasses detalhes de implementação.footnote:[Eu ganhei a vida por alguns anos escrevendo código para Django, antes de resolver estudar como os campos dos modelos Django eram implementados. Só então aprendi sobre descritores e metaclasses.]

Nos últimos anos, algumas metaclasses na biblioteca padrão de Python foram
substituídas por outros mecanismos, sem afetar a API pública de seus pacotes. A
forma mais simples de resguardar estas APIs para o futuro é oferecer uma classe
normal, da qual usuários podem então criar subclasses para acessar a
funcionalidade fornecida pela metaclasse, como fiz em vários exemplos.

Para encerrar nossa conversa sobre metaprogramação de classes, vou mostrar
o pequeno exemplo de metaclasse mais interessante que encontrei durante
minha pesquisa para esse capítulo.((("", startref="CMissue24")))((("",
startref="MCconsider24")))

<<<
[[metahack_sec]]
=== Um _hack_ de metaclasse com `+__prepare__+`

Quando((("class metaprogramming", "&#x005F;&#x005F;prepare&#x005F;&#x005F; method", id="CMjprepare24",
secondary-sortas="prepare")))((("&#x005F;&#x005F;prepare&#x005F;&#x005F;",
id="prepare24"))) atualizei esse capítulo para a segunda edição, procurei
exemplos simples mas interessantes para substituir o código de
`LineItem` no exemplo da loja de comida a granel, que não exige mais
o uso de metaclasses desde o Python 3.6.

João S. O. Bueno—mais conhecido como JS na comunidade Python brasileira,
me apresentou a ideia de metaclasse mais curiosa que já vi.

Uma aplicação de sua ideia é criar uma classe que gera constantes numéricas automaticamente:

[source, python]
----
include::../code/24-class-metaprog/autoconst/autoconst_demo.py[tags=AUTOCONST]
----

Sim, isto funciona do jeito que está! Este código é um doctest em _autoconst_demo.py_.

Aqui está a classe base fácil de usar `AutoConst` , e a metaclasse por trás dela, implementadas em _autoconst.py_:

[source, python]
----
include::../code/24-class-metaprog/autoconst/autoconst.py[tags=AUTOCONST]
----

É só isso.

Claramente, o truque está em `WilyDict`.

Quando Python processa o espaço de nomes da classe do usuário e lê `banana`, ele
procura aquele nome no mapeamento fornecido por `+__prepare__+`: uma instância
de `WilyDict` que implementa o método `+__missing__+`, tratado na
https://fpy.li/ct[«Seção 3.5.2»] (vol.1).

A instância de `WilyDict` inicialmente não contém uma chave
`'banana'`, então o método `+__missing__+` é acionado. Ele cria um item na hora,
com a chave `'banana'` e o valor `0`, e devolve este valor. Python se
contenta com isso, e daí tenta acessar `'coconut'`. `WilyDict` imediatamente
adiciona aquele item com o valor `1`, e o devolve. O mesmo acontece com
`'vanilla'`, que é então mapeado para `2`.

Já vimos `+__prepare__+` e `+__missing__+` antes.
A verdadeira inovação é a forma como JS as juntou.

Aqui está o código-fonte de `WilyDict`, também de _autoconst.py_:

[source, python]
----
include::../code/24-class-metaprog/autoconst/autoconst.py[tags=WilyDict]
----

Enquanto experimentava, descobri que Python procurava `+__name__+` no espaço de
nomes da classe em construção, fazendo com que `WilyDict` acrescentasse um item
`+__name__+` e incrementasse `+__next_value+`. Eu então inseri uma instrução
`if` em `+__missing__+`, para levantar um `KeyError` quando uma chave se parece com
um atributo _dunder_.

Me diverti adicionando mais funcionalidades a `AutoConstMeta` e `AutoConst`, mas em vez de compartilhar meus experimentos, vou deixar vocês se divertirem, brincando com o _hack_ genial de JS.

<<<
Aqui estão algumas ideias:

* Torne possível obter o nome da constante a partir do valor. Por exemplo, `Flavor[2]` devolveria `'vanilla'`. Você pode fazer isso implementando `+__getitem__+` em `AutoConstMeta`. Desde o
Python 3.9, é possível implementar
`+__class_getitem__+` na própria `AutoConst`.

* Suporte a iteração sobre a classe, implementando `+__iter__+` na metaclasse.
Eu faria `+__iter__+` produzir as constantes na forma de pares `(name, value)`.

* Implemente uma nova variante de `Enum`. Isso é um empreendimento
épico, pois o pacote `enum` está cheio de truques, incluindo a metaclasse
`EnumMeta`, com centenas de linhas de código e um método `+__prepare__+` bem complicado.

Divirta-se!

[NOTE]
====

O método especial `+__class_getitem__+` foi introduzido no Python 3.9 para
suportar tipos genéricos, como parte da
https://fpy.li/pep585[_PEP 585—Type Hinting Generics in Standard Collections_]
(Dicas de Tipos Genéricas em Coleções Padrão).
Graças a `+__class_getitem__+`, os mantenedores de Python não
precisaram escrever uma nova metaclasse para que os tipos embutidos
implementassem `+__getitem__+`, de modo que fosse possível escrever dicas de
tipo genéricas, tal como `list[int]`. Esse é um recurso limitado, mas
representativo, de um caso de uso mais amplo para metaclasses: implementar
operadores e outros métodos especiais para funcionarem ao nível da classe, tal
como fazer a própria classe iterável, como as subclasses de `Enum`.((("",
startref="prepare24")))((("", startref="CMjprepare24")))

====

=== Para encerrar

Metaclasses, bem((("class metaprogramming", "useful applications of metaclasses")))((("metaclasses", "useful applications of"))) como decoradores de classes e `+__init_subclass__+`, são úteis para:

- Registro de subclasses
- Validação estrutural de subclasses
- Aplicar decoradores a muitos métodos ao mesmo tempo
- Serialização de objetos
- Mapeamento objeto-relacional
- Persistência automática de objetos
- Implementar métodos especiais a nível de classe
- Implementar recursos de classes encontrados em outras linguagens, como
https://fpy.li/24-17[_traits_] e
https://fpy.li/c3[«programação orientada a aspecto»]

Em alguns casos, a metaprogramação de classes também pode ajudar em questões de desempenho, executando tarefas no momento da importação que de outra forma seriam executadas repetidamente durante a execução.

Para finalizar, vamos nos lembrar do conselho final de Alex Martelli em seu ensaio
_Pássaros aquáticos e as ABCs_, na https://fpy.li/cq[«Seção 13.5»] (vol.2):

[quote]
____

E _não_ defina ABCs customizadas (ou metaclasses) em código de produção. Se você
sentir uma forte necessidade de fazer isso, aposto que é um caso da síndrome de
"todos os problemas se parecem com um prego" em alguém que acabou de ganhar um
novo martelo brilhante—você (e os futuros mantenedores de seu código) serão
mais felizes se limitando a código simples e direto, e evitando tais profundezas.
____

Acredito que o conselho de Martelli se aplica não apenas a ABCs e metaclasses,
mas também a hierarquias de classe, sobrecarga de operadores, decoradores de funções, descritores, decoradores de classes e fábricas de classes usando `+__init_subclass__+`.

Em princípio, essas ferramentas poderosas existem para suportar o desenvolvimento de bibliotecas e frameworks.
Naturalmente, as aplicações devem _usar_ tais ferramentas, na forma oferecida pela biblioteca padrão de Python ou por pacotes externos.
Mas _implementá-las_ em código de aplicações é frequentemente resultado de uma abstração prematura.

[quote, DHH, criador de Ruby on Rails]
____
Bons frameworks são extraídos, não inventados.footnote:[Esta frase é muito citada.
Encontrei uma citação direta antiga no blog de DHH, em https://fpy.li/24-19[post] de 2005.]
____


=== Resumo do capítulo

Este((("class metaprogramming", "overview of"))) capítulo começou com uma revisão dos atributos encontrados em objetos classe, como `+__qualname__+` e o método `+__subclasses__()+`.
A seguir, vimos como a classe embutida `type` pode ser usada para criar classes durante a execução.

Apresentei o método especial `+__init_subclass__+` na primeira versão de uma
classe base `Checked`, projetada para substituir dicas de tipo de atributos em
subclasses definidas pelo usuário por instâncias do descritor `Field`, que usam
construtores para validar o tipo dos atributos durante a execução.

Implementei a mesma ideia com um decorador de classes `@checked`, que acrescenta
recursos a classes definidas pelo usuário, de forma similar ao que pode ser
feito com `+__init_subclass__+`. Vimos que `+__init_subclass__+` ou um
decorador de classes não conseguem configurar `+__slots__+` dinamicamente, pois atuam
apenas após a criação da classe, e `+__slots__+` tem que ser definido antes.

Desvendamos os conceitos de "momento de importação" (_import time_)
e "momento de execução" (_run time_)
com experimentos mostrando a ordem na qual o código Python é executado quando
módulos, descritores, decoradores de classe e `+__init_subclass__+` são acionados.

Nossa exploração de metaclasses começou com uma explicação geral de `type` como uma metaclasse,
e sobre como metaclasses definidas pelo usuário podem implementar `+__new__+`, para customizar as classes que criam.
Vimos então nossa primeira metaclasse customizada, o clássico exemplo `MetaBunch`, usando
`+__slots__+`.
A seguir, outro experimento com etapas de avaliação demonstrou como os métodos `+__prepare__+` e
`+__new__+` de uma metaclasse são invocados antes de `+__init_subclass__+`
e decoradores de classe, oferecendo mais oportunidades para customização de classes.

Estudamos uma terceira versão de uma fábrica de classes `Checked`, com descritores `Field` e
uma configuração customizada de `+__slots__+`, seguida de
considerações gerais sobre o uso de metaclasses na prática.

Por fim, vimos o hack `AutoConst`, inventado por João S. O. Bueno, baseado na
brilhante ideia de uma metaclasse com `+__prepare__+` devolvendo um mapeamento
que implementa `+__missing__+`. Em menos de 20 linhas de código, _autoconst.py_
demonstra o poder da combinação de técnicas de metaprogramação no Python.

Nunca encontrei outra linguagem como Python, fácil para iniciantes, prática para
profissionais e empolgante para hackers. Obrigado, Guido van Rossum e todos que
a fazem ser assim.


[[ch21-furtherreading]]
=== Para saber mais

Caleb Hattingh—um dos revisores técnicos((("class metaprogramming", "further
reading on"))) desse livro—escreveu o pacote https://fpy.li/24-20[_autoslot_],
fornecendo uma metaclasse para a criação automática do atributo `+__slots__+` em
uma classe definida pelo usuário, através da inspeção do bytecode de
`+__init__+` e da identificação de todas as atribuições a atributos de `self`.
Além de útil, esse pacote é um excelente exemplo para estudo: são apenas 74
linhas de código em _autoslot.py_, incluindo 20 linhas de comentários que
explicam as partes mais difíceis.

As referências essenciais deste capítulo na documentação de Python são
https://fpy.li/by[«Personalizando a criação de classe»] no capítulo
_Modelo de Dados_ da _Referência da Linguagem Python_, que cobre
`+__init_subclass__+` e metaclasses.
A https://fpy.li/c4[«documentação da classe `type`»]
na página _Funções Embutidas_, e
https://fpy.li/bt[«Atributos especiais»] do capítulo _Tipos embutidos_ da _Biblioteca Padrão de Python_
também são leituras fundamentais.

Na _Biblioteca Padrão de Python_, a
https://fpy.li/bz[«documentação do módulo `types`»]
trata de duas funções introduzidas no Python 3.3 que simplificam a
metaprogramação de classes: `types.new_class` e `types.prepare_class`.

Decoradores de classes foram formalizados na
https://fpy.li/24-25[_PEP 3129—Class Decorators_]
(Decoradores de Classes), escrita por Collin Winter, com a
implementação de referência desenvolvida por Jack Diederich.
A palestra 
https://fpy.li/24-26[_Class Decorators: Radically Simple_]
(Decoradores de Classes: Radicalmente Simples) na PyCon 2009,
também de Jack Diederich, é
uma rápida introdução a este recurso. Além de `@dataclass`, um exemplo
interessante—e mais simples—de decorador de classes na biblioteca padrão de
Python é https://fpy.li/7q[`functools.total_ordering`], que gera métodos
especiais para comparação de objetos.

Para metaclasses, a principal referência na documentação de Python é a
https://fpy.li/pep3115[_PEP 3115—Metaclasses in Python 3000_]
(Metaclasses no Python 3000), onde o método especial `+__prepare__+` foi proposto.

<<<
O _Python in a Nutshel_ 3rd. ed., de Alex Martelli, Anna Ravenscroft, e Steve Holden,
é uma referência, mas foi escrito antes da https://fpy.li/pep487[_PEP 487_]
simplificar o processo de customizar classes na sua criação. O principal exemplo
de metaclasse no livro—`MetaBunch`—ainda é válido, pois não pode ser escrito com
mecanismos mais simples.
O _Effective Python_ 2nd. ed. (Addison-Wesley), de
Brett Slatkin, traz vários exemplos atualizados de técnicas de criação de
classes, incluindo metaclasses.

Para aprender sobre as origens da
metaprogramação de classes no Python, recomendo o artigo de Guido van Rossum de
2003,
https://fpy.li/24-28[_Unifying types and classes in Python 2.2_]
(Unificando tipos e classes no Python 2.2). O texto se aplica também ao
Python moderno, pois cobre as chamadas "classes novo estilo"—o comportamento padrão no Python 3—incluindo descritores e
metaclasses. Uma das referências citadas por Guido é _Putting Metaclasses to
Work: a New Dimension in Object-Oriented Programming_, de Ira R. Forman e Scott
H. Danforth (Addison-Wesley), livro para o qual ele deu cinco estrelas na
_Amazon.com_, escrevendo o seguinte comentário:

[quote]
____

*Este livro contribuiu para o projeto das metaclasses no Python 2.2*

Pena que esteja fora de catálogo; sempre me refiro a ele como o melhor tutorial
que conheço para o difícil tópico da herança múltipla cooperativa, suportada
pelo Python através da função `super()`.footnote:[Comprei um exemplar usado, e
achei uma leitura muito difícil. Não cheguei ao final.]

____

Se você curte metaprogramação, talvez gostaria que Python suportasse
o recurso definitivo de metaprogramação: macros sintáticas,
como as oferecidas pela família de linguagens Lisp e—mais recentemente—pelo Elixir e pelo Rust.
Macros sintáticas são mais poderosas e menos sujeitas a erros que as macros primitivas de substituição de código da linguagem C.
Elas são funções especiais que reescrevem código-fonte para código padronizado, usando uma sintaxe customizada, antes da etapa de compilação,
permitindo a desenvolvedores introduzir novas estruturas na linguagem sem modificar o compilador.
Como a sobrecarga de operadores, macros sintáticas podem ser mal usadas.
Mas, desde que a comunidade entenda e controle as desvantagens, elas suportam abstrações poderosas e amigáveis,
como as DSLs (_Domain-Specific Languages_, Linguagens de Domínio Específico).

<<<
Em setembro de 2020, Marc Shannon, um dos mantenedores de Python, publicou a
https://fpy.li/pep638[_PEP 638—Syntactic Macros_], uma proposta de macros sintáticas.
Um ano após sua publicação inicial (quando escrevo essas linhas), a PEP 638 ainda era um rascunho e não havia discussões contínuas sobre ela.
Claramente não é uma prioridade muito alta entre os mantenedores de Python.
Eu gostaria de ver a PEP 638 sendo melhor discutida e, por fim, aprovada.
Macros sintáticas permitiriam à comunidade Python experimentar com novos recursos controversos,
tal como o "operador morsa" (_operador walrus_) (https://fpy.li/pep572[PEP 572]),
casamento de padrões (https://fpy.li/pep634[PEP 634]) e regras alternativas para avaliação de dicas de tipo
(PEPs https://fpy.li/pep563[563] e
https://fpy.li/pep649[649]),
antes que se fizessem modificações permanentes no núcleo da linguagem.
Nesse meio tempo, podemos sentir o gosto das macros sintáticas com o pacote
https://fpy.li/24-29[MacroPy].


<<<
.Ponto de vista
****

Vou((("class metaprogramming", "Soapbox discussion")))((("Soapbox sidebars", "programming language design"))) iniciar o último ponto de vista no livro com uma longa citação de Brian Harvey e Matthew Wright, dois professores de ciência da computação da Universidade da California (Berkeley e Santa Barbara). Em seu livro, _Simply Scheme: Introducing Computer Science_ ("Simplesmente Scheme: Introduzindo a Ciência da Computação") (MIT Press), Harvey e Wright escreveram:


[quote, Brian Harvey e Matthew Wright, no prefácio de <em>Simply Scheme</em>]
____
Há duas escolas de pensamento sobre o ensino de ciência da computação. Podemos representar as duas visões de uma forma caricatural, assim:


. *A visão conservadora*: Programas de computador se tornaram muito grandes e
complexos para serem apreendidos pela mente humana. Portanto, a tarefa da
educação na ciência da computação é ensinar os estudantes como se disciplinarem,
de tal forma que 500 programadores medíocres possam se juntar e produzir um
programa que atende às especificações.

. *A visão radical*: Programas de computador se tornaram muito grandes e
complexos para serem apreendidos pela mente humana. Portanto, a tarefa da
educação na ciência da computação é ensinar os estudantes como expandir suas
mentes até abarcar os programas, aprendendo a pensar com um vocabulário
de ideias maiores, mais poderosas e mais flexíveis que as óbvias. Cada
unidade de pensamento programático deve gerar uma grande recompensa para as
capacidades do programa.footnote:[Brian Harvey e Matthew Wright, _Simply Scheme_
(MIT Press, 1999), p. xvii. O livro completo está disponível em
https://fpy.li/24-30[Berkeley.edu].]

____

As descrições exageradas de Harvey e Wright versam sobre o ensino de ciência da computação,
mas também se aplicam ao projeto de linguagens de programação.
Neste ponto você já deve ter adivinhado que concordo com a visão "radical",
e acredito que Python foi projetado neste espírito.

****

<<<

****

A ideia de propriedade é um grande avanço em relação à abordagem "métodos de acesso desde o início",
praticamente exigida em Java e suportada pela geração de _getters/setters_ por atalhos de teclado nas IDEs Java.
A principal vantagem das propriedades é nos permitir começar a criar nossos programas
simplesmente expondo atributos publicamente—no espírito do _KISS_—sabendo
que um atributo público pode se tornar uma propriedade a qualquer momento sem quebrar código existente.
Mas a ideia de descritor vai muito além disso,
fornecendo um framework para abstrair lógica repetitiva para acessar atributos.
Este framework é tão eficaz que mecanismos essenciais de Python o utilizam por baixo dos panos.

Outra ideia poderosa são as funções como objetos de primeira classe,
abrindo caminho para funções de ordem superior. E acontece que a
combinação de descritores e funções de ordem superior permite a unificação de
funções e métodos. O `+__get__+` de uma função cria um objeto método sob demanda,
vinculando a instância ao argumento `self`. Isto é
elegante.footnote:[_Machine Beauty: Elegance and the Heart of Technology_
(Beleza de Máquina: A Elegância e o Coração da Tecnologia), de David Gelernter
(Basic Books), começa com uma discussão intrigante sobre elegância e estética em
obras de engenharia, de pontes a software. Os capítulos posteriores não são tão
bons, mas o início vale o preço.]

Por fim, temos a ideia de classes como objetos de primeira
classe. É uma façanha impressionante de design que uma linguagem acessível para
iniciantes forneça abstrações poderosas, como fábricas de classes, decoradores de
classes, e metaclasses completas definidas pelo usuário. Melhor ainda, os
recursos avançados estão integrados de forma que não atrapalham a programação
casual (eles na verdade ajudam, por trás dos panos). A conveniência e o
sucesso de frameworks como o Django e o SQLAlchemy devem muito às metaclasses.
Ao longo dos anos, a metaprogramação de classes em Python está se tornando cada
vez mais simples, pelo menos para os casos de uso comuns. Os melhores recursos
da linguagem são aqueles que beneficiam a todos, mesmo que alguns usuários de
Python não os conheçam. Mas esses usuários sempre podem aprender, e criar a
próxima grande biblioteca.

Mande notícias sobre suas contribuições ao ecossistema e à comunidade de Python!
****

<<<

