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

[quote, Brian W. Kernighan and 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 ser usada para criar uma nova classe a qualquer momento, sem usar a palavra-chave `class`.

Decoradores de classes também são funções, mas são projetados para inspecionar, modificar ou mesmo 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 anteriormente.

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.
Mais ainda, Python 3.6 implementou a https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma customização mais simples 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 você queira escrever o próximo framework formidável de Python, essas 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 não representam mais as soluções mais simples, tendo em vista os novos 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 novos exemplos 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 envolvem a remoção de código tornado redundante por formas novas e e mais simples de resolver o mesmo problema.
Isso se aplica tanto a código em produção quando 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]]
=== Classes como objetos

Como((("class metaprogramming", "classes as objects"))) acontece com a maioria das entidades programáticas de Python, classes também são objetos.
Toda classe tem alguns atributos definidos no Modelo de Dados de Python, documentados na seção https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes["4.13. 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__+`, and `+__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, em um modelo de classe Django, tal como https://fpy.li/24-2[`Ox`] (EN), há uma classe interna 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 (_PEP 3155—Nome qualificado para classes e funções_)] (EN).

`+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—que mantêm uma referência forte para a superclasse em seus atributos `+__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 aparecem na lista devolvida pela função `dir(…)`.
====

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


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

Nós((("class metaprogramming", "built-in class factory"))) normalmente 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 essa classe simples:

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

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

Usando o construtor `type`, podemos criar `MyClass` durante a execução, com o seguinte código:

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

Aquela chamada a `type` é funcionalmente equivalente ao bloco sob a instrução `class MyClass…` anterior.

Quando Python lê uma instrução `class`, invoca `type` para construir um objeto classe com os parâmetros abaixo:

`name`::
    O identificador que aparece após a palavra-chave `class`, por exemplo, `MyClass`.
`bases`::
    A tupla de superclasses passadas entre parênteses após o identificador da classe, ou
    `(object,)`, caso nenhuma superclasse seja mencionada na instrução `class`.
`dict`::
    Um mapeamento entre nomes de atributo e valores. Invocáveis se tornam métodos, como vimos na <<methods_are_descriptors_sec>>. Outros valores se tornam atributos de classe.

[NOTE]
====
O construtor `type` aceita argumentos nomeados opcionais, que são ignorados por `type`, mas que são passados como recebidos para `+__init_subclass__+`, que deve consumi-los.
Vamos estudar esse método especial na <<enhancing_with_init_subclass_sec>>, mas não vou tratar do uso de argumentos nomeados. Para saber mais sobre isso, por favor leia a https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma customização mais simples da criação de classes_)] (EN).
====

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 <<ch_dataclass>> também vimos `typing.NamedTuple` e `@dataclass`.
Todas essas 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 uma _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
----

Chato... cada nome de campo aparece três vezes, e essa repetição sequer nos garante 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` em tempo real. 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 chamada como `namedtuple`: nome da classe, seguido dos nomes dos atributos separados por espaços, em um única string.
<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> Uma instância do registro é mutável.
<6> A classe recém-criada herda de `object`—não tem qualquer relação com nossa fábrica.

O código para `record_factory` está no <<record_factory_ex>>.footnote:[Agradeço a 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 que se comporta como uma `tuple`.
<3> Cria uma tupla de nomes de atributos; esse será o atributo `+__slots__+` da nova classe.
<4> Essa 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 to tipo devolvido porque em caso contrário, o Mypy não verificaria o código dentro do 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.
Se a anotação fosse apenas `-> type`, significaria que `record_factory` devolve uma classe—e isso estaria correto.
Mas a anotação `-> type[tuple]` é mais precisa: indica que a classe devolvida será uma subclasse de `tuple`.

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 carregado com
`+__slots__+`, `+__init__+`, `+__iter__+`, e `+__repr__+`, sendo os útimos três métodos de instância.

Poderíamos ter dado qualquer outro nome ao atributo de classe `+__slots__+`, mas então 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 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 <<slots_sec>>.

[WARNING]
====
Instâncias de classes criadas por `record_factory` não são serializáveis--isto é, elas não podem ser exportadas com a função `dump` do módulo `pickle`. Resolver este problema está além do escopo desse exemplo, que tem por objetivo mostrar a classe `type` funcionando em um caso de uso simples. Para uma solução completa, estude o código-fonte de pass:[<a href="https://fpy.li/24-4"><span class=
"keep-together"><code>collections.namedtuple</code></span></a>]; 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 o comando `class`, e a melhora automaticamente, acrescentando funcionalidade.((("", startref="CMfacfun24")))


[[enhancing_with_init_subclass_sec]]
=== Apresentando pass:[__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 (_PEP 487—Uma customização mais simples da criação de classes_)].
Falamos pela primeira vez do método especial para descritores `+__set_name__+` na <<auto_storage_sec>>.
Agora vamos estudar `+__init_subclass__+`.

No <<ch_dataclass>>, vimos como `typing.NamedTuple` e `@dataclass` permitem a programadores usarem a instrução `class` para especificar atributos para uma nova classe, que então é aperfeiçoada pela fábrica de classes com a adição automática de métodos essenciais, como `+__init__+`, `+__repr__+`, `+__eq__+`, etc.

Ambas as fábricas de classes leem as dicas de tipo na instrução `class` do usuário para aperfeiçoar a classe. Essas 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 se valem das 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, e possivelmente essa é a razão para `typing.NamedTuple` e `@dataclass` sequer tentarem.
Entretanto, algums tipos que são também classes concretas podem ser usados com `Checked`.
Isso 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 <<checked_class_top_ex>>.
<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 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 um 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")] (EN) 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 nessa chamada ao construtor:

[source, python]
----
blockbuster = 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 o descontinuado `typing.Callable`.
<2> Essa é a dica de tipo `Callable` mínima; o parâmetro de tipo e o tipo devolvido para `constructor` são ambos implicitamente `Any`.
<3> Para verificação 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] (EN).] 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` dado.
<6> Se `constructor` gerar qualquer dessas exceções, geramos 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 gerada, o `value` é armazenado no `+instance.__dict__+`.

Em `+__set__+`, precisamos capturar `TypeError` e `ValueError`, pois os construtores embutidos podem gerar qualquer dos dois, dependendo do argumento.
Por exemplo, `float(None)` gera um `TypeError`, mas `float('A')` gera um `ValueError`.
Por outro lado, `float('8')` não causa qualquer erro, e devolve `8.0`.
E assim eu aqui declaro que, nesse 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 <<checked_class_top_ex>>.
====

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

[[checked_class_top_ex]]
.initsub/checkedlib.py: os métodos mais importante 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 ocultar 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 <<problems_annot_runtime_sec>> para uma discussão dos problemas com essas funções.
<2> `+__init_subclass__+` é chamado quando 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 <<init_subclass_not_typical_box>>.
<3> `+super().__init_subclass__()+` não é estritamente necessário, mas deve ser invocado para ajudar outras classes que implementem `+.__init_subclass__()+` na mesma árvore de herança. Veja a <<mro_section>>.
<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> Essa chamada a `setattr` aciona `+Checked.__setattr__+`, apresentado no <<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> Esse 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.


[[init_subclass_not_typical_box]]
.pass:[__init_subclass__] não é um método de classe típico
****
O decorador `@classmethod` nunca é usado com `+__init_subclass__+`, mas isso não quer dizer muita coisa, pois o método especial `+__new__+` se comporta como um método de classe mesmo sem `@classmethod`.
O primeiro argumento que Python passa para `+__init_subclass__+` é uma classe.
Entretanto, essa nunca é a classe onde `+__init_subclass__+` é implementado, mas sim uma subclasse recém-definida daquela classe.
Isso é diferente de `+__new__+` e de qualquer outro método de classe que eu conheço.
Assim, acho que `+__init_subclass__+` não é um método de classe no sentido usual, e é errado nomear seu primeiro argumento `cls`. A
pass:[<a href="https://docs.python.org/pt-br/3/reference/datamodel.html#object.__init_subclass__">documentação de <code>__init_suclass__</code></a>] 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..."footnote:[NT: Em 17 de setembro de 2023, a primeira frase está traduzida de forma confusa na documentação em português. Optamos por traduzir aqui diretamente da documentação em inglês.].
****

Vamos examinar os métodos restantes da classe `Checked`,
continuando do <<checked_class_top_ex>>.
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. Isso é 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. Nesse caso isso foi 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`.footnote:[O sutil conceito de descritor dominante foi explicado na <<overriding_descriptor_sec>>.]
<4> Caso contrário, o atributo `name` é desconhecido, e uma exceção será gerada por
`+__flag_unknown_attrs+`.
<5> Cria uma mensagem de erro útil, listando todos os argumentos inesperados, e gera um `AttributeError`.
Este é um raro exemplo do tipo especial `NoReturn`, tratado na <<noreturn_sec>>.
<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 pass:[__init_subclass__] não pode configurar pass:[__slots__]?

O((("&#x005F;&#x005F;slots&#x005F;&#x005F;"))) atributo `+__slots__+` só é efetivo se for um dos elementos do espaço de nomes da classe passado para `+type.__new__+`.
Acrescentar `+__slots__+` a uma classe existente não tem qualquer efeito.
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 próprio 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 bombástico, e implementar uma metaclasse.
Veremos como configurar `+__slots__+` dinamicamente na <<metclass101_sec>>.

Antes da https://fpy.li/pep487[PEP 487] (EN) 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.
É o tópico de nossa próxima seção.((("", startref="initsub24")))((("", startref="CMinitsub24")))


=== Melhorando classes com 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 é um invocável que se comporta de forma similar a um decorador de funções:
recebe uma classe decorada como argumento, e deve devolver um classe para substituir a classe decorada. Decoradores de classe frequentemente devolvem a própria classe decorada, após injetar nela mais métodos pela definição de atributos.
Provavelmente, a razão mais comum para escolher um decorador de classes, em vez do mais simples
`+__init_subclass__+`, é evitar interferência com outros recursos da classe, como herança e metaclasses.footnote:[Essa justificativa aparece no resumo da https://fpy.li/24-9[PEP 557–Data Classes (_Classes de Dados_)] (EN), para explicar porque ela foi implementada como um decorador de classes.]

Nessa 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 que classes são instâncias de `type`. Essas dicas de tipo sugerem fortemente que este é um decorador de classes: ele recebe uma classe e devolve uma classe.
<2> `+_fields+` agora é uma função de alto nível definida mais tarde no módulo (no <<checkeddeco_methods_ex>>).
<3> Substituir cada atributo devolvido por `+_fields+` por uma instância do descritor `Field` é o que `+__init_subclass__+` fazia no <<checked_class_top_ex>>. 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.

[role="pagebreak-before less_space"]
Todas as funções no primeiro nível de _checkeddeco.py_ estão prefixadas com um sublinhado, exceto o decorador `checked`.
Essa convenção para a 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 <<checked_class_top_ex>> e no <<checked_class_bottom_ex>>.

Observe que a função `+_fields+` exerce dois papéis em _checkeddeco.py_.
Ela é usada como uma função regular 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 mais 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 "importação" (_import time_) versus "execução" (_runtime_), mas estes termos não têm definições precisas e há uma zona cinzenta entre eles.

Na importação, o interpretador:

. Analisa o código-fonte de módulo _.py_ em uma passagem, de cima até embaixo. É 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 da análise e a compilação serem definitivamente atividades de "importação", outras coisas podem acontecer durante o processo, pois quase todos os comandos ou instruções no Python são executáveis, no sentido de poderem potencialmente rodar código do usuário e modificar o estado do programa do usuário.

Em especial, a instrução `import` não é meramente uma declaraçãofootnote:[Compare com a instrução `import` em Java, que é apenas uma declaração para informar o compilador que determinados pacotes são necessários.], pois na verdade ela executa todo o código no nível superior de um módulo, quando este é importado para o processo 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. Aquele código no primeiro nível 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 regular.

Tudo isso é bastante abstrato e sútil, então vamos realizar alguns experimentos para ver o que acontece, e quando.


[[evaldemo_sec]]
==== Experimentos com a fase de avaliação (_evaluation time_)

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. Essa combinação incomum é útil nesses experimentos, para mostrar a ordem temporal das mudanças que um decorador de classes e `+__init_subclass__+` podem aplicar a uma 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
----

Observe que as linhas exibidas por _builderlib.py_ tem um `@` como prefixo.

Vamos agora voltar a atenção para _evaldemo.py_, que vai acionar método 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 pincipal.

As chamadas a `print` em _evaldemo.py_ tem 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 vão aparecer se você não fechar o console após o experimento anterior, pois _builderlib.py_ já estará carregado.
<2> Isso 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://docs.python.org/pt-br/3/reference/datamodel.html#creating-the-class-object["Criando o objeto classe"], no capítulo
https://docs.python.org/pt-br/3/reference/datamodel.html["Modelo de Dados"] da referência de Python.

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

Se você executar _evaldemo.py_ como um script, vai ver a mesma saída do <<evaldemo_console_ex>>, com linhas extras logo antes do final.
As linhas adicionais são o resultado da execução de `main()` (veja o <<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> Acionado por `+super().__init__()+` em `+Klass.__init__+`.
<3> Acionado por `obj.method_a()` em `main`; o `method_a` foi injetado por
`+SuperA.__init_subclass__+`.
<4> Acionado por `obj.method_b()` em `main`; `method_b` foi injetado por `deco`.
<5> Acionado por `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 desse capítulo—e desse livro.((("", startref="CMimport24")))


[[metclass101_sec]]
=== Introdução às metaclasses
____
[quote, Tim Peters, inventor do algoritmo timsort e um produtivo colaborador de Python]

&#x5b;Metaclasses&#x5d; são uma mágica tão profunda que 99% dos usuários jamais deveria se preocupar com elas. Quem se pergunta se precisa delas, não precisa (quem realmente precisa de metaclasses sabe disso 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 no c.l.p._)]. 
Essa é outra parte da mesma mensagem de 23 de dezembro de 2002, citada no <<preface>>. 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 Engenhocas e Bugigangas para representar 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[Diagrama MGN com metaclasse e classe.]

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 deixa claro que `str`, `object`, e `LineItem` são instâncias de `type`, pois todas são classes.
image::../images/flpy_2402.png[Diagrama de classes UML com as relações de `object` e `type`.]

[NOTE]
====
As classes `object` e `type` tem uma relação singular: `object` é uma instância de `type`, e `type` é uma subclasse de `object`. Essa 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 apenas metaclasses são também subclasses de `type`.
Essa é a mais importante relação 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.

[role="width-60"]
[[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[Diagramas de classe UML com as relações de `Iterable` e `ABCMeta`.]

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 demosntram 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.
Isso 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 essa instrução `class`, Python invoca `+MetaKlass.__new__+` com os seguintes argumentos:

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

`cls_name`:: A string `Klass`.

`bases`:: A tupla com um único elemento `(SuperKlass,)` (ou com mais elementos, em caso de herança múltipla).

`cls_dict`::
+
--
Um mapeamento como esse:

[source]
----
{x: 42, `+__init__+`: <function __init__ 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 também aplicar processamento adicional à classe recém-criada, antes de devolvê-la para Python. Python então invoca `+SuperKlass.__init_subclass__+`, passando a classe que criamos, e então aplicando um decorador de classe, se algum estiver presente.
Finalmente, Python vincula o objeto classe a seu nome no espaço de nomes circundante—normalmente o espaço de nomes global do módulo, se a instrução `class` foi uma instrução no primeiro nível.

O processamento mais comum realizado no `+__new__+` de uma metaclasse é adicionar ou substituir itens no `cls_dict`—o mapeamento que representa o espaço de nomes da classe em construção. Por exemplo, antes de chamar `+super().__new__+`, 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 pass:[<a href="https://fpy.li/pynut3" class="orm:hideurl"><em>Python in a Nutshell</em>, 3ª ed.</a>], 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 exmeplo 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]
----

Lembre-se que `Checked` atribui nomes aos descritores `Field` em subclasses, baseada em dicas de tipo de variáveis de classe, que não se tornam atributos na classe, já que não tem valores.

Subclasses de `Bunch`, por outro lado, usam atributos de classe reais com valores, que então se tornam os valores default dos atributos de instância.
O `+__repr__+` gerado omite os argumentos para atributos 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.
Isso bloqueia a instanciação e posterior atribuição a atributos não declarados:

[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 gosto de nomear o primeiro argumento `meta_cls` (`mcs` é uma alternativa comum). Os três argumentos restantes são os mesmos da assinatura de três argumentos de `type()`, quando chamada diretamente para criar uma classe.
<3> `defaults` vai manter um mapeamento de nomes de atributos e seus valores default.
<4> Isso irá 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.
Acreditamos em _falhar rápido_ como melhor prática, então não queremos ignorar silenciosamente os itens em excesso. Uma solução rápida e eficiente é extrair um item de `kwargs` e tentar defini-lo na instância, gerando propositalmente um `AttributeError`.
<7> `+__repr__+` devolve uma string que se parece com uma chamada ao construtor—por exemplo, `Point(x=3)`, omitindo os argumentos nomeados com valores default.
<8> Inicializa o espaço de nomes para a nova classe.
<9> Itera sobre o espaço de nomes da classe do usuário.
<10> Se um `name` _dunder_ (com sublinhados como prefixo e sufixo) é encontrado, copia o item para o espaço de nomes da nova classe, a menos que ele já esteja lá. Isso 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 por ser capaz 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 a fase de avaliação, agora com uma metaclasse.((("", startref="MCexample24")))


==== Experimento com a fase de avaliação de metaclasses

Essa((("metaclasses", "metaclass evaluation time experiment", id="MCtime24"))) é uma variação do <<evaldemo_sec>>, acrescentando uma metaclasse à mistura.
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 racionalidade e aplica três técnicas diferentes de metaprogramação juntas a `Klass`:
um decorador, uma classe base usando `+__init_subclass__+`, e uma metaclasse customizada.
Se você fizer isso com código em produção, por favor não me culpe.
Repito, 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 a fase 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 manter o espaço de nomes da classe em construção, revelando um pouco mais sobre o funcionamento interno de 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 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, onde vai manter o espaço de nomes da classe em construção.
<3> Devolve uma instância de `NosyDict` para ser usado como o espaço de nomes.
<4> `cls_dict` é uma instância de `NosyDict` devolvida por `+__prepare__+`.
<5> `+type.__new__+` exige um `dict` real como último argumento, então passamos a ele o atributo `data` de `NosyDict`, herdado de `UserDict`.
<6> Injeta um método na classe recém-criada.
<7> Como sempre, `+__new__+` precisa devolver 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 oferecer um
`OrderedDict` para manter 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 no <<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 antes disso são resultado da 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> `+__set_name__+`, `+__init_subclass__+` e o decorador são invocados nessa ordem, após o método
`+__new__+` da metaclasse devolver a classe recém-criada.

Se executarmos _evaldemo_meta.py_ como um script, `main()` é chamado, e algumas outras 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")))


=== Uma solução para Checked usando uma metaclasse

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

.Uma contação de história
****
Nosso _checkedlib.py_ usando `+__init_subclass__+` é um sucesso na empresa, e em qualquer dado momento nossos servidores de produção guardam milhões de instâncias de subclasses de  `Checked` em suas memórias.

Analisando o perfil de uma prova de conceito, descobrimos que usar `+__slots__+` pode reduzi os custos de hospedagem, por duas razões:

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

****


O módulo _metaclass/checkedlib.py_, que estudaremos a seguir, é um substituto instantâneo para  _initsub/checkedlib.py_.
Os doctests embutidos 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:

[source, python]
----
include::../code/24-class-metaprog/checked/metaclass/checked_demo.py[tags=MOVIE_DEMO]
----

Essa 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`.

Observe 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.

Por exemplo, uma classe `Movie` usando a nova _checkedlib.py_ é uma instância de `CheckedMeta` e uma subclasse de `Checked`.
Adicionalmente, 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`, exibida no <<checked_field_meta_ex>>.

A classe descritora `Field` agora 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__+`.

[[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 de cada instância para os campos são armazenados nos atributos de instância  `+_title+`, `+_year+` e `+_box_office+` de `Movie`. Observe 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.]

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> Determina `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 para a metaclasse que controla este exemplo.

[[checked_metaclass_ex]]
.metaclass/checkedlib.py: tha 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ó melhora a classe se seu `cls_dict` não incluir `+__slots__+`. Se `+__slots__+` já está presente, assume que essa é 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 um 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 base `Checked`, a partir da qual os usuários dessa biblioteca criarão subclasses para melhorar suas classes, como `Movie`.

O código desta versão de `Checked` é o mesmo da `Checked` em _initsub/checkedlib.py_
(listada no <<checked_class_top_ex>> 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__+`, que se tornou redundante: o acréscimo de `+__slots__+` à classe definida pelo usuário impede a definiçã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]
----
====

Isso 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__+`:
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 o 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 "classes com novo estilo", descritores e propriedades.
together with so-called "new-style classes," descriptors, and properties.

É 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 sendo a forma de especificar a metaclasse a ser usada, algo que no Python 3 é feito com a sintaxe `class Bunch(metaclass=MetaBunch):`.

Nenhum dos acréscimos que mencionei na <<metaclass_modern_features_sec>> quebrou código existente que usava metaclasses. Mas código legado com metaclasses frequentemente pode ser simplificado através do uso daqueles recursos, especialmente se for possível ignorar versões de Python anteriores à 3.6—versões que não são mais mantidas.


==== 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
(_TypeError: 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 gerar 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 metaclasse 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]
====
Posso aceitar a solução de uma metaclasse com duas metaclasses base, implementada para atender um prazo.
Na minha experiência, a programação de metaclasses sempre leva mais tempo que o esperado, tornando essa abordagem arriscada ante um prazo inflexível.
Se você fizer isso e cumprir o prazo previsto, seu código pode conter bugs sutis.
E mesmo na ausência de bugs conhecidos, essa 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 bilbioteca padrão de Python 3.9.
As metaclasses mais conhecidas provavelmnete são `abc.ABCMeta`, `typing.NamedTupleMeta` e
`enum.EnumMeta`.
Nenhuma delas foi projetada com a intenção de aparecer explicitamente no código do usuário.
Podemos considerá-las detalhes de implementação.

Apesar de ser possível fazer metaprogramação bem maluca 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 essas APIs para o futuro é oferecer uma classe regular, da qual usuários podem então criar subclasses para acessar a funcionalidade fornecida pela metaclasse. como fizemos em nossos exemplos.

Para encerrar nossa conversa sobre metaprogramação de classes, vou compartilhar com vocês o pequeno exemplo de metaclasse mais sofisticado 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, precisava encontrar exemplos simples mas reveladores, para substituir o código de `LineItem` no exemplo da loja de comida a granel, que não precisava mais de metaclasses desde o Python 3.6.

A ideia de metaclasse mais interessante e mais simples me foi dada por
João S. O. Bueno—mais conhecido como JS na comunidade Python brasileira.
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, esse código funciona como exibido! Aquilo acima é 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`.
`WilyDict` implementa `+__missing__+`, tratado na <<missing_method>>.
A instância de `WilyDict` inicialmente não contém uma chave `'banana'`, então o método
`+__missing__+` é acionado.
Ele cria um item em tempo real, com a chave `'banana'` e o valor `0`, e devolve esse valor.
Python se contenta com isso, e daí tenta recuperar `'coconut'`.
`WilyDict` imediatamente adiciona aquele item com o valor `1`, e o devolve.
O mesmo acontece com `'vanilla'`, que é então mapeado para `2`.

[role="pagebreak-before less_space"]
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 gerar um `KeyError` para chaves que se parecem com atributos _dunder_.

O pacote _autoconst.py_ tanto exige quanto ilustra o mecanismo de criação dinâmica de classes de Python.

Me diverti muito 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 possivel 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
pass:[<a href="https://fpy.li/24-16"><code>__class_getitem__</code></a>]
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 seria um empreeendimento complexo, pois o pacote `enum` está cheio de armadilhas, incluindo a metaclasse `EnumMeta`, com centenas de linhas de código e um método `+__prepare__+` nem um pouco trivial.

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_)] (EN).
Graças a `+__class_getitem__+`, os desenvolvedores principais 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 restrito, mas representativo, de um caso de uso mais amplo para metaclasses: implementar operadores e outros métodos especiais para funcionarem a nível de classes, 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 baseada em objetos
- Implementar métodos especiais a nível de classe
- Implementar recursos de classes encontrados em outras linguagens, tal como https://fpy.li/24-17[traits (_traços_)] (EN) e https://pt.wikipedia.org/wiki/Programa%C3%A7%C3%A3o_orientada_a_aspecto[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 <<waterfowl_essay>>:

[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 poderosas ferramentas 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, David Heinemeier Hansson, criador de Ruby on Rails]
____
Bons frameworks são extraídos, não inventados.footnote:[Essa frase é muito citada. Encontrei uma citação direta antiga em um https://fpy.li/24-19[post] de 2005 no blog de DHH.]
____


=== 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.

O método especial `+__init_subclass__+` foi introduzido, com a 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 de `Field`, que usam construtores para impor o tipo daqueles atributos durante a execução.

A mesma ideia foi implementada 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 nem `+__init_subclass__+` nem um decorador de classes podem configurar `+__slots__+` dinamicamente, pois operam apenas após a criação da classe.

Os conceitos de "[momento/tempo de] importação" e "[momento/tempo de] execução" foram esclarecidos com experimentos mostrando a ordem na qual o código Python é executado quando módulos, descritores, decoradores de classe e  `+__init_subclass__+` estão envolvidos.

Nossa exploração de metaclasses começou com um 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 o tempo de avaliação demonstrou como os métodos `+__prepare__+` e
`+__new__+` de uma metaclasse são invocados mais cedo que `+__init_subclass__+` e decoradores de classe, oferecendo oportunidades para uma customização de classes mais profunda.

A terceira versão de uma fábrica de classes `Checked`, com descritores `Field` e uma configuração customizada de `+__slots__+` foi apresentada, 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://docs.python.org/pt-br/3/reference/datamodel.html#customizing-class-creation["3.3.3. Personalizando a criação de classe"] no capítulo "Modelos de Dados" da _Referência da Linguagem Python_, que cobre `+__init_subclass__+` e metaclasses. A https://docs.python.org/pt-br/3/library/functions.html#type[documentação da classe ++type++] na página "Funções Embutidas", e https://docs.python.org/pt-br/3/library/stdtypes.html#special-attributes["4.13. Atributos especiais"] do capítulo "Tipos embutidos" na _Biblioteca Padrão de Python_ também são leituras fundamentais.

Na _Biblioteca Padrão de Python_, a https://docs.python.org/pt-br/3/library/types.html[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` and `types.prepare_class`.

Decoradores de classes foram formalizados na https://fpy.li/24-25[PEP 3129—Class Decorators (_Decoradores de Classes_)] (EN), escrita por Collin Winter, com a implemetação de referência desenvolvida por Jack Diederich. A palestra "Class Decorators: Radically Simple"  (_Decoradores de Classes: Radicalmente Simples_. Aqui o https://fpy.li/24-26[video] (EN)), na PyCon 2009, também de Jack Diederich, é uma rápida introdução a esse recurso.
Além de `@dataclass`, um exemplo interessante—e mais simples—de decorador de classes na bilbioteca padrão de Python é https://docs.python.org/pt-br/3/library/functools.html#functools.total_ordering[`functools.total_ordering`] (EN), 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 introduzido.

O pass:[<a href="https://fpy.li/pynut3" class="orm:hideurl"><em>Python in a Nutshell</em></a>], 3ª ed., de Alex Martelli, Anna Ravenscroft, e Steve Holden, é uma referência, mas foi escrito antes da https://fpy.li/pep487[PEP 487—Simpler customization of class creation (_PEP 487—Uma customização mais simples da criação de classes_)] ser publicada. O principal exemplo de metaclasse no livro—`MetaBunch`—ainda é válido, pois não pode ser escrito com mecanismos mais simples.
O pass:[<a href="https://fpy.li/effectpy" class="orm:hideurl"><em>Effective Python</em></a>], 2ª ed. (Addison-Wesley), de Brett Slatkin, traz vários exemplos atualizados de técnicas de criação de classes, incluindo metaclasses.

[role="pagebreak-before less_space"]
Para aprender sobre as origens da metaprogramação de classes no Python, recomento 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_)] (EN).
O texto se aplica também ao Python moderno, pois cobre o quê era então chamado de "novo estilo" de  semântica de classes—a semântica default 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_, acrescentando 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 desafiadora.]
____

Se você gosta de 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 gerencie 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 desenvolvedores principais de Python, publicou a
https://fpy.li/pep638[PEP 638—Syntactic Macros (_Macros Sintáticas_)] (EN), defendendo exatamente isso.
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 desenvolvedores principais 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] (EN)),
casamento de padrões (https://fpy.li/pep634[PEP 634] (EN)) e regras alternativas para avaliação de dicas de tipo
(PEPs https://fpy.li/pep563[563] (EN) e
https://fpy.li/pep649[649] (EN)),
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].


[role="pagebreak-before less_space"]
.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 and 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 caricatual, 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 suas 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é que os programas caibam ali, aprendendo a pensar com um vocabulário de ideias maiores, mais poderosas e mais flexíveis que aquelas ó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 texto completo está disponível em https://fpy.li/24-30[Berkeley.edu] (EN).]
____

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.
Nesse ponto você já deve ter adivinhado que concordo com a visão "radical",
e acredito que Python foi projetado nesse espírito.

A ideia de propriedade é um grande passo adiante, comparado com a abordagem "métodos de acesso desde o início",
praticamente exigida em Java e suportada pela geração de _getters/setters_ através de atalhos do teclado por 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.
Esse framework é tão eficiente que mecanismos essenciais de Python o utilizam por baixo dos panos.

Outra ideia poderosa são as funções como objetos de primeira classe, pavimentando o 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 produz um objeto método em tempo real, vinculando a instância ao argumento `self`. Isso é 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((("data structures", see="also data class builders; dictionaries and sets; object references; sequences; Unicode text versus bytes")))((("functions, as first-class objects", see="also decorators and closures")))((("control flow", see="also asynchronous programming; concurrent executors; concurrency models; iterators; generators; with, match, and else blocks")))((("metaprogramming", see="also attribute descriptors; class metaprogramming; dynamic attributes and properties")))((("sets", see="also dictionaries and sets")))((("frozenset", see="also dictionaries and sets")))((("byte sequences", see="also Unicode text versus bytes")))((("text files, handling", see="also Unicode text versus bytes")))((("encoding", see="also Unicode text versus bytes")))((("decoding", see="also Unicode text versus bytes")))((("NFC", see="Normalization Form C")))((("UCA", see="Unicode Collation Algorithm")))((("mutable objects", see="also object references")))((("first-class functions", see="functions, as first-class objects")))((("closures", see="decorators and closures")))((("function decorators", see="decorators and closures")))((("design patterns", see="functions, design patterns with first-class")))((("Pythonic objects", see="also objects")))((("patterns", see="functions, design patterns with first-class; pattern matching")))((("special methods", see="also sequences, special methods for")))((("protocols", see="also interfaces")))((("typing map", see="also type hints (type annotations)")))((("interfaces", see="also goose typing; protocols")))((("subclassing", see="inheritance and subclassing")))((("MRO", see="method resolution order")))((("multiple inheritance", see="also inheritance and subclassing")))((("gradual type system", see="also type hints (type annotations)")))((("type hints (type annotations)", "gradual typing", see="also gradual type system")))((("match blocks", see="with, match, and else blocks")))((("spinners (loading indicators)", see="also network I/O")))((("executors, delegating tasks to", see="also concurrent executors")))((("attributes", see="also attribute descriptors; dynamic attributes and properties")))((("attribute descriptors", see="also attributes; dynamic attributes and properties")))((("properties", see="computed properties; dynamic attributes and properties")))((("descriptors", see="also attribute descriptors")))((("PUG", see="Python Users Group")))((("PSF", see="Python Software Foundation")))((("I/O (input/output)", see="network I/O")))((("LRU", see="Least Recently Used")))((("classes", see="also protocols")))((("binary sequences", see="also Unicode text versus bytes")))((("data model", see="Python Data Model")))((("data attributes", see="attributes")))((("data descriptors", see="overriding descriptors")))((("recycling", see="garbage collection")))((("methods, as callable objects", see="also sequences, special methods for; special methods"))) a ideia de classes como objetos de primeira classe.
É uma façanha marcante do projeto, que uma linguagem acessível para um iniciante forneça abstrações poderosas, como fábricas de classe, decoradores de classe, e metaclasses completas e definidas pelo usuário.
Melhor ainda, os recursos avançados estão integrados de forma a não atrapalhar a programação casual (eles na verdade ajudam nisso, por trás da cortina).
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.

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