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?[1]
The Elements of Programming Style
Metaprogramação de classes é a arte de criar ou personalizar 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, tais 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, o Python 3.6 implementou a PEP 487—Simpler customization of class creation (PEP 487—Uma personalização mais simples da criação de classes), fornecendo métodos especiais para tarefas que antes exigiam metaclasses ou decoradores de classe.[2]
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 do Python, essas são suas ferramentas de trabalho. |
Todo 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 Metaclasses no mundo real é 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 do Python para todas as classes.
Como acontece com a maioria das entidades programáticas do Python, classes também são objetos.
Toda classe tem alguns atributos definidos no Modelo de Dados do Python, documentados na seção "4.13. Atributos Especiais" do capítulo "Tipos Embutidos" da Biblioteca Padrão do Python.
Três destes atributos já apareceram várias vezes no livro:
__class__
, __name__
, and __mro__
.
Outros atributos de classe padrão são:
cls.__bases__
-
A tupla de classes base da classe.
cls.__qualname__
-
O 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
Ox
(EN), há uma classe interna chamadaMeta
. O__qualname__
deMeta
éOx.Meta
, mas seu__name__
é apenasMeta
. A especificação para este atributo está na PEP 3155—Qualified name for classes and functions (PEP 3155—Nome qualificado para classes e funções) (EN). cls.__subclasses__()
-
Este 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 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 sobrepor este método, para personalziar 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 |
Agora, se classe é um objeto, o que é a classe de uma classe?
Nós 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:
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:
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 o 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çãoclass
. 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 |
A classe type
é uma 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:
>>> type(7)
<class 'int'>
>>> type(int)
<class 'type'>
>>> type(OSError)
<class 'type'>
>>> class Whatever:
... pass
...
>>> type(Whatever)
<class 'type'>
Vamos criar metaclasses personalizadas na Introdução às metaclasses.
Agora, vamos usar a classe embutida type
para criar uma função que constrói classes.
A biblioteca padrão contém uma função fábrica de classes que já apareceu várias vezes aqui: collections.namedtuple
.
No [data_class_ch] 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:
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
:
>>> 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 Exemplo 1 mostra como ela deve funcionar.
record_factory
, uma fábrica de classes simpleslink:code/24-class-metaprog/factories.py[role=include]
-
A fábrica pode ser chamada como
namedtuple
: nome da classe, seguido dos nomes dos atributos separados por espaços, em um única string. -
Um
repr
agradável. -
Instâncias são iteráveis, então elas podem ser convenientemente desempacotadas em uma atribuição…
-
…ou quando são passadas para funções como
format
. -
Uma instância do registro é mutável.
-
A classe recém-criada herda de
object
—não tem qualquer relação com nossa fábrica.
link:code/24-class-metaprog/factories.py[role=include]
-
O usuário pode fornecer os nomes dos campos como uma string única ou como um iterável de strings.
-
Aceita argumentos como os dois primeiros de
collections.namedtuple
; devolvetype
—isto é, uma classe que se comporta como umatuple
. -
Cria uma tupla de nomes de atributos; esse será o atributo
__slots__
da nova classe. -
Essa função se tornará o método
__init__
na nova classe. Ela aceita argumentos posicionais e/ou nomeados.[4] -
Produz os valores dos campos na ordem dada por
__slots__
. -
Produz um
repr
agradável, iterando sobre__slots__
eself
. -
Monta um dicionário de atributos de classe.
-
Cria e devolve a nova classe, invocando o construtor de
type
. -
Converte
names
separados por espaços ou vírgulas em uma lista destr
.
O Exemplo 2 é 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 Exemplo 2 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_section].
Warning
|
Instâncias de classes criadas por |
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.
Tanto __init_subclass__
quanto __set_name__
foram propostos na
PEP 487—Simpler customization of class creation (PEP 487—Uma personalizaçã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 [data_class_ch], 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, tais 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 verificadores de tipo 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 verificação de tipo durante a execução, e possivelmente essa é a razão para |
O Exemplo 3 mostra como usar Checked
para criar uma classe Movie
.
Movie
de Checked
link:code/24-class-metaprog/checked/initsub/checkedlib.py[role=include]
-
Movie
herda deChecked
—que definiremos mais tarde, no Exemplo 5. -
Cada atributo é anotado com um construtor. Aqui usei tipos embutidos.
-
Instâncias de
Movie
devem ser criadas usando argumentos nomeados. -
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 Exemplo 3 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.[5]
Quando chamado sem argumentos, o construtor deve devolver um valor default de seu tipo.[6]
Esse é o comportamento padrão de construtores embutidos no 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:
link:code/24-class-metaprog/checked/initsub/checkedlib.py[role=include]
Os construtores são usados para validação durante a instanciação, e quando um atributo é definido diretamente em uma instância:
link:code/24-class-metaprog/checked/initsub/checkedlib.py[role=include]
Warning
|
Subclasses de
Checked e a verificação estática de tiposEm um arquivo de código fonte .py contendo uma instância movie.year = 'MCMLXXII' Entretanto, o Mypy não consegue detectar erros de tipo nessa chamada ao construtor: blockbuster = Movie(title='Avatar', year='MMIX') Isso porque Por outro lado, se você declarar um campo de uma subclasse de |
Vamos ver agora a implementação de checkedlib.py.
A primeira classe é o descritor Field
, como mostra o Exemplo 4.
Field
link:code/24-class-metaprog/checked/initsub/checkedlib.py[role=include]
-
Lembre-se, desde o Python 3.9, o tipo
Callable
para anotações é a ABC emcollections.abc
, e não o descontinuadotyping.Callable
. -
Essa é a dica de tipo
Callable
mínima; o parâmetro de tipo e o tipo devolvido paraconstructor
são ambos implicitamenteAny
. -
Para verificação durante a execução, usamos o embutido
callable
.[7] O teste contratype(None)
é necessário porque o Python entendeNone
em um tipo comoNoneType
, a classe deNone
(e portanto invocável), mas esse é um construtor inútil, que apenas devolveNone
. -
Se
Checked.__init__
definirvalue
como…
(o objeto embutidoEllipsis
), invocamos o construtor sem argumentos. -
Caso contrário, invocamos o
constructor
com ovalue
dado. -
Se
constructor
gerar qualquer dessas exceções, geramos umTypeError
com uma mensagem útil, incluindo os nomes do campo e do construtor; por exemplo,'MMIX' não é compatível com year:int
. -
Se nenhuma exceção for gerada, o
value
é armazenado noinstance.__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 |
Vamos agora nos concentrar na classe Checked
, que dividi em duas listagens. O Exemplo 5 mostra a parte inicial da classe, incluindo os métodos mais importantes para esse exemplo.
O restante dos métodos está no Exemplo 6.
Checked
link:code/24-class-metaprog/checked/initsub/checkedlib.py[role=include]
-
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 do Python ≥ 3.10, invocariainspect.get_annotations
em vez disso. Reveja a [problems_annot_runtime_sec] para uma discussão dos problemas com essas funções. -
__init_subclass__
é chamado quando uma subclasse da classe atual é definida. Ele recebe aquela nova subclasse como seu primeiro argumento—e por isso nomeei o argumentosubclass
em vez do habitualcls
. Para mais informações sobre isso, veja __init_subclass__ não é um método de classe típico. -
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]. -
Itera sobre
name
econstructor
em cada campo… -
…criando um atributo em
subclass
com aquelename
vinculado a um descritorField
, parametrizado comname
econstructor
. -
Para cada
name
nos campos da classe… -
…obtém o
value
correspondente dekwargs
e o remove dekwargs
. Usar…
(o objetoEllipsis
) como default nos permite distinguir entre argumentos com valorNone
de argumentos ausentes.[8] -
Essa chamada a
setattr
acionaChecked.__setattr__
, apresentado no Exemplo 6. -
Se houver itens remanescentes em
kwargs
, seus nomes não correspondem a qualquer dos campos declarados, e__init__
vai falhar. -
Esse erro é informado por
__flag_unknown_attrs
, listado no Exemplo 6. 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.
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 o 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
documentação de __init_suclass__
chama o argumento de cls
, mas explica: "…chamado sempre que se cria uma subclasse da classe que o contém. cls
é então a nova subclasse…"[9].
Vamos examinar os métodos restantes da classe Checked
,
continuando do Exemplo 5.
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
link:code/24-class-metaprog/checked/initsub/checkedlib.py[role=include]
-
Intercepta qualquer tentativa de definir um atributo de instância. Isso é necessário para evitar a definição de um atributo desconhecido.
-
Se o
name
do atributo é conhecido, busca odescriptor
correspondente. -
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 comoField
.[10] -
Caso contrário, o atributo
name
é desconhecido, e uma exceção será gerada por__flag_unknown_attrs
. -
Cria uma mensagem de erro útil, listando todos os argumentos inesperados, e gera um
AttributeError
. Este é um raro exemplo do tipo especialNoReturn
, tratado na [noreturn_sec]. -
Cria um
dict
a partir dos atributos de um objetoMovie
. Eu chamaria este método de_as_dict
, mas segui a convenção iniciada com o método_asdict
emcollections.namedtuple
. -
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 Exemplo 6.
Em record_factory.py (no Exemplo 2), 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.
O 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.
O 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 O que acontece quando: importação versus execução.
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 Introdução às metaclasses.
Antes da PEP 487 (EN) simplificar a personalizaçã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.
Um 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, tais como herança e metaclasses.[11]
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 Exemplo 7).
Movie
decorada com @checked
link:code/24-class-metaprog/checked/decorator/checkeddeco.py[role=include]
A única diferença entre o Exemplo 7 e o Exemplo 3 é 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 Exemplo 3, na Apresentando __init_subclass__.
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 Exemplo 4.
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 Exemplo 8.
link:code/24-class-metaprog/checked/decorator/checkeddeco.py[role=include]
-
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. -
_fields
agora é uma função de alto nível definida mais tarde no módulo (no Exemplo 9). -
Substituir cada atributo devolvido por
_fields
por uma instância do descritorField
é o que__init_subclass__
fazia no Exemplo 5. Aqui há mais trabalho a ser feito… -
Cria um método de classe a partir de
_fields
, e o adiciona à classe decorada. O comentáriotype: ignore
é necessário, porque o Mypy reclama quetype
não tem um atributo_fields
. -
Funções ao nível do módulo, que se tornarão métodos de instância da classe decorada.
-
Adiciona cada um dos
instance_methods
acls
. -
Devolve a
cls
decorada, cumprindo o contrato básico de um decorador de classes.
Todas as funções no primeiro nível de checkeddeco.py estão prefixadas com um sublinhado, exceto o decorador checked
.
Essa convenção 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 Exemplo 9 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 Exemplo 9.
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 Exemplo 5 e no Exemplo 6.
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.
link:code/24-class-metaprog/checked/decorator/checkeddeco.py[role=include]
O módulo checkeddeco.py implementa um decorador de classes simples mas usável.
O @dataclass
do Python faz muito 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 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.
Programadores Python falam de "importação" (import time) versus "execução" (runtime), mas estes termos não tem 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ção[12], 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", tais como escrever em um arquivo de log ou conectar-se a um banco de dados.[13]
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.
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 tem 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 |
Vamos começar examinando builderlib.py, dividido em duas partes: o Exemplo 10 e o Exemplo 11.
link:code/24-class-metaprog/evaltime/builderlib.py[role=include]
-
Essa é uma fábrica de classes para implementar…
-
…um método
__init_subclass__
. -
Define uma função para ser adicionada à subclasse na atribuição abaixo.
-
Um decorador de classes.
-
Função a ser adicionada à classe decorada.
-
Devolve a classe recebida como argumento.
Continuando builderlib.py no Exemplo 11…
link:code/24-class-metaprog/evaltime/builderlib.py[role=include]
-
Uma classe descritora para demonstrar quando…
-
…uma instância do descritor é criada, e quando…
-
…
__set_name__
será invocado durante a criação da classeowner
. -
Como os outros métodos, este
__set__
não faz nada, exceto exibir seus argumentos.
Se importarmos builderlib.py no console do Python, veremos o seguinte:
>>> 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 Exemplo 12).
link:code/24-class-metaprog/evaltime/evaldemo.py[role=include]
-
Aplica um decorador.
-
Cria uma subclasse de
Builder
para acionar seu__init_subclass__
. -
Instancia o descritor.
-
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 Exemplo 13.
>>> 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
-
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. -
Isso sinaliza que o Python começou a ler o corpo de
Klass
. Neste momento o objeto classe ainda não existe. -
A instância do descritor é criada e vinculada a
attr
, no espaço de nomes que o Python passará para o construtor default do objeto classe:type.__new__
. -
Neste ponto, a função embutida do Python
type.__new__
já criou o objetoKlass
e invoca__set_name__
em cada instância das classes do descritor que oferecem aquele método, passandoKlass
como argumentoowner
. -
type.__new__
então chama__init_subclass__
na superclasse deKlass
, passandoKlass
como único argumento. -
Quando
type.__new__
devolve o objeto classe, o Python aplica o decorador. Neste exemplo, a classe devolvida pordeco
está vinculada aKlass
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
"Criando o objeto classe", no capítulo
"Modelo de Dados" da referência do Python.
Observe que a função main()
de evaldemo.py (no Exemplo 12) não foi executada durante a sessão no console (no Exemplo 13), 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 Exemplo 13, com linhas extras logo antes do final.
As linhas adicionais são o resultado da execução de main()
(veja o Exemplo 14).
$ ./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
-
As 10 primeiras linhas—incluindo essa—são as mesma que aparecem no Exemplo 13.
-
Acionado por
super().__init__()
emKlass.__init__
. -
Acionado por
obj.method_a()
emmain
; omethod_a
foi injetado porSuperA.__init_subclass__
. -
Acionado por
obj.method_b()
emmain
;method_b
foi injetado pordeco
. -
Acionado por
obj.attr = 999
emmain
.
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.
[Metaclasses] 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).[14]
inventor do algoritmo timsort e um produtivo colaborador do Python
Uma metaclasse é uma fábrica de classes.
Diferente de record_factory
, do Exemplo 2,
uma metaclasse é escrita como uma classe.
Em outras palavras, uma metaclasse é uma classe cujas instâncias são classes.
A Figura 1 usa a Notação Engenhocas e Bugigangas para representar uma metaclasse: uma engenhoca que produz outra engenhoca.
Pense no modelo de objetos do Python: classes são objetos, portanto cada classe deve ser uma instância de alguma outra classe.
Por default, as classes do 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:
>>> 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 Figura 2 pode ajudar você a contemplar essa estranha realidade.
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.
Note
|
As classes |
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
:
>>> 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 Figura 3 ilustra essa relação fundamental.
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.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 personalizar suas instâncias implementando métodos especiais, como demosntram as próximas seções.
Para 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:
class Klass(SuperKlass, metaclass=MetaKlass):
x = 42
def __init__(self, y):
self.y = y
Para processar essa instrução class
, o 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:
{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 o Python. O 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, o 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 Por que __init_subclass__ não pode configurar __slots__?.
O método __new__
de uma metaclasse é o lugar ideal para configurar __slots__
.
A próxima seção mostra como fazer isso.
A metaclasse MetaBunch
, apresentada aqui, é uma variação do último exemplo no Capítulo 4 do Python in a Nutshell, 3ª ed., de Alex Martelli, Anna Ravenscroft, e Steve Holden, escrito para rodar sob Python 2.7 e 3.5.[15]
Assumindo o uso do Python 3.6 ou mais recente, pude simplificar ainda mais o código.
Mas primeiro vamos ver o que a classe base Bunch
oferece:
link:code/24-class-metaprog/metabunch/from3.6/bunch.py[role=include]
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:
link:code/24-class-metaprog/metabunch/from3.6/bunch.py[role=include]
Vamos agora mergulhar no elegante código de MetaBunch
, no Exemplo 15.
MetaBunch
e a classe Bunch
link:code/24-class-metaprog/metabunch/from3.6/bunch.py[role=include]
-
Para criar uma nova metaclasse, herdamos de
type
. -
__new__
funciona como um método de classe, mas a classe é uma metaclasse, então gosto de nomear o primeiro argumentometa_cls
(mcs
é uma alternativa comum). Os três argumentos restantes são os mesmos da assinatura de três argumentos detype()
, quando chamada diretamente para criar uma classe. -
defaults
vai manter um mapeamento de nomes de atributos e seus valores default. -
Isso irá ser injetado na nova classe.
-
Lê
defaults
e define o atributo de instância correspondente, com o valor extraído dekwargs
, ou um valor default. -
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 dekwargs
e tentar defini-lo na instância, gerando propositalmente umAttributeError
. -
__repr__
devolve uma string que se parece com uma chamada ao construtor—por exemplo,Point(x=3)
, omitindo os argumentos nomeados com valores default. -
Inicializa o espaço de nomes para a nova classe.
-
Itera sobre o espaço de nomes da classe do usuário.
-
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, tais como__qualname__
e__module__
. -
Se
name
não for um dunder, acrescentaname
a__slots__
e armazena seuvalue
emdefaults
. -
Cria e devolve a nova classe.
-
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.
Essa é uma variação do Experimentos com a fase de avaliação (evaluation time), acrescentando uma metaclasse à mistura. O módulo builderlib.py é o mesmo de antes, mas o script principal é agora evaldemo_meta.py, listado no Exemplo 16.
link:code/24-class-metaprog/evaltime/evaldemo_meta.py[role=include]
-
Importa
MetaKlass
de metalib.py, que veremos no Exemplo 18. -
Declara
Klass
como uma subclasse deBuilder
e uma instância deMetaKlass
. -
Este método é injetado por
MetaKlass.__new__
, como veremos adiante.
Warning
|
Em nome da ciência, o Exemplo 16 desafia qualquer racionalidade e aplica três técnicas diferentes de metaprogramação juntas a |
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 Exemplo 17 mostra a primeira parte do código de metalib.py—o restante está no Exemplo 18.
NosyDict
link:code/24-class-metaprog/evaltime/metalib.py[role=include]
Escrevi a classe NosyDict
para sobrepor __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 do Python.
A principal atração de metalib.py é a metaclasse no Exemplo 18.
Ela implementa o método especial __prepare__
, um método de classe que o 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:
|
MetaKlass
link:code/24-class-metaprog/evaltime/metalib.py[role=include]
-
__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 o Python invoca__prepare__
. -
O Python invoca
__prepare__
em uma metaclasse para obter um mapeamento, onde vai manter o espaço de nomes da classe em construção. -
Devolve uma instância de
NosyDict
para ser usado como o espaço de nomes. -
cls_dict
é uma instância deNosyDict
devolvida por__prepare__
. -
type.__new__
exige umdict
real como último argumento, então passamos a ele o atributodata
deNosyDict
, herdado deUserDict
. -
Injeta um método na classe recém-criada.
-
Como sempre,
__new__
precisa devolver o objeto que acaba de ser criado—neste caso, a nova classe. -
Definir
__repr__
em uma metaclasse permite personalizar orepr()
de objetos classe.
O principal caso de uso para __prepare__
antes do 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 Um hack de metaclasse com __prepare__.
Importar metalib.py no console do Python não é muito empolgante.
Observe o uso de %
para prefixar as linhas geradas por esse módulo:
>>> import metalib
% metalib module start
% MetaKlass body
% metalib module end
Muitas coisas acontecem quando importamos evaldemo_meta.py, como visto no Exemplo 19.
>>> 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
-
As linhas antes disso são resultado da importação de builderlib.py e metalib.py.
-
O Python invoca
__prepare__
para iniciar o processamento de uma instruçãoclass
. -
Antes de analisar o corpo da classe, o Python acrescenta
__module__
e__qualname__
ao espaço de nomes de uma classe em construção. -
A instância do descritor é criada…
-
…e vinculada a
attr
no espaço de nomes da classe. -
Os métodos
__init__
e__repr__
são definidos e adicionados ao espaço de nomes. -
Após terminar o processamento do corpo da classe, o Python chama
MetaKlass.__new__
. -
__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 Exemplo 20).
$ ./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
-
As primeiras 21 linhas—incluindo esta—são as mesmas que aparecem no Exemplo 19.
-
Acionado por
obj.method_c()
emmain
;method_c
foi injetado porMetaKlass.__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.
Não 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.
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 deField.__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:
link:code/24-class-metaprog/checked/metaclass/checked_demo.py[role=include]
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 Figura 4. A Notação Engenhocas e Bugigangas 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 Exemplo 21.
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__
.
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.O Exemplo 21 mostra o código-fonte de Field
, com os textos explicativos descrevendo apenas as mudanças nessa versão.
Field
com storage_name
e __get__
link:code/24-class-metaprog/checked/metaclass/checkedlib.py[role=include]
-
Determina
storage_name
a partir do argumentoname
. -
Se
__get__
recebeNone
como argumentoinstance
, o descritor está sendo lido desde a própria classe gerenciada, não de uma instância gerenciada. Neste caso devolvemos o descritor. -
Caso contrário, devolve o valor armazenado no atributo chamado
storage_name
. -
__set__
agora usasetattr
para definir ou atualizar o atributo gerenciado.
O Exemplo 22 mostra o código para a metaclasse que controla este exemplo.
CheckedMeta
link:code/24-class-metaprog/checked/metaclass/checkedlib.py[role=include]
-
__new__
é o único método implementado emCheckedMeta
. -
Só melhora a classe se seu
cls_dict
não incluir__slots__
. Se__slots__
já está presente, assume que essa é a classe baseChecked
e não uma subclasse definida pelo usuário, e cria a classe sem modificações. -
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 docls_dict
—o espaço de nomes da classe em construção, que o Python passa como último argumento para o__new__
da metaclasse. -
Itera sobre
type_hints
para… -
…criar um
Field
para cada atributo anotado… -
…sobrescreve o item correspondente em
cls_dict
com a instância deField
… -
…e acrescenta o
storage_name
do campo à lista que usaremos para… -
…preencher o
__slots__
nocls_dict
—o espaço de nomes da classe em construção. -
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 Exemplo 5 e no Exemplo 6), com três modificações:
-
O acréscimo de um
__slots__
vazio, para sinalizar aCheckedMeta.__new__
que esta classe não precisa de processamento especial. -
A remoção de
__init_subclass__
, cujo trabalho agora é feito porCheckedMeta.__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 Exemplo 23 é a listagem completa da versão final de Checked
.
Checked
link:code/24-class-metaprog/checked/metaclass/checkedlib.py[role=include]
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.
Metaclasses são poderosas mas complexas. Antes de se decidir a implementar uma metaclasse, considere os pontos a seguir.
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 personalizada para definir automaticamente o nome de um descritor.[16]
__init_subclass__
-
Fornece uma forma de personalizar 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 a principal razão para usar
__prepare__
: fornecer umOrderedDict
para armazenar o espaço de nomes de uma classe em construção. 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 do 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.
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 Recursos modernos simplificam ou substituem as metaclasses 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 do Python anteriores à 3.6—versões que não são mais mantidas.
Se sua declaração de classe envolver duas ou mais metaclasses, você verá essa intrigante mensagem de erro:
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
:
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 deabc.ABCMeta
quanto dePersistentMeta
, usando herança múltipla, e faça dela a única metaclasse deRecord
.[17]
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. |
Além de type
, existem apenas outras seis metaclasses em toda a bilbioteca padrão do 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 princípio do menor espanto, de forma que a maioria dos usuários possa de fato considerar metaclasses detalhes de implementação.[18]
Nos últimos anos, algumas metaclasses na biblioteca padrão do 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.
Quando 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:
link:code/24-class-metaprog/autoconst/autoconst_demo.py[role=include]
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:
link:code/24-class-metaprog/autoconst/autoconst.py[role=include]
É só isso.
Claramente, o truque está em WilyDict
.
Quando o 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.
O 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
.
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:
link:code/24-class-metaprog/autoconst/autoconst.py[role=include]
Enquanto experimentava, descobri que o 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 do 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__
emAutoConstMeta
. Desde o Python 3.9, épossível implementar__class_getitem__
na própriaAutoConst
. -
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 pacoteenum
está cheio de armadilhas, incluindo a metaclasseEnumMeta
, com centenas de linhas de código e um método__prepare__
nem um pouco trivial.
Divirta-se!
Note
|
O método especial |
Metaclasses, bem 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 traits (traços) (EN) e 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]:
E não defina ABCs personalizadas (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 muito 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 do Python ou por pacotes externos. Mas implementá-las em código de aplicações é frequentemente resultado de uma abstração prematura.
Bons frameworks são extraídos, não inventados.[19]
criador do Ruby on Rails
Este capítulo começou com uma revisão dos atributos encontrados em objetos classe, tais 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 personalziar as classes que criam.
Vimos então nossa primeira metaclasse personalizada, 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 personalização de classes mais profunda.
A terceira versão de uma fábrica de classes Checked
, com descritores Field
e uma configuração personalizada 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 o Python, fácil para iniciantes, prática para profissionais e empolgante para hackers. Obrigado, Guido van Rossum e todos que a fazem ser assim.
Caleb Hattingh—um dos revisores técnicos desse livro—escreveu o pacote 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 do Python são "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 documentação da classe type na página "Funções Embutidas", e "4.13. Atributos especiais" do capítulo "Tipos embutidos" na Biblioteca Padrão do Python também são leituras fundamentais.
Na Biblioteca Padrão do Python, a 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 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 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 muito mais simples—de decorador de classes na bilbioteca padrão do Python é functools.total_ordering
(EN), que gera métodos especiais para comparação de objetos.
Para metaclasses, a principal referência na documentação do Python é a
PEP 3115—Metaclasses in Python 3000 (Metaclasses no Python 3000),
onde o método especial __prepare__
foi introduzido.
O Python in a Nutshell, 3ª ed., de Alex Martelli, Anna Ravenscroft, e Steve Holden, é uma referência, mas foi escrito antes da PEP 487—Simpler customization of class creation (PEP 487—Uma personalizaçã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 Effective Python, 2ª ed. (Addison-Wesley), de Brett Slatkin, traz vários exemplos atualizados de técnicas de criação de classes, incluindo metaclasses.
Para aprender sobre as origens da metaprogramação de classes no Python, recomento o artigo de Guido van Rossum de 2003, "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:
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()
.[20]
Se você gosta de metaprogramação, talvez gostaria que o 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 personalizada, 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 do Python, publicou a 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 do 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) (PEP 572 (EN)), correspondência/casamento de padrões (PEP 634 (EN)) e regras alternativas para avaliação de dicas de tipo (PEPs 563 (EN) e 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 MacroPy.
Vou 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:
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.[21]
no prefácio de Simply Scheme
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 eu concordo com a visão "radical", e acredito que o 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 do 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.[22]
Por fim, temos 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, tais 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 afetar a adequação do Python para 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 do 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 do Python!
Any
. Escrevi a dica to tipo devolvido porque em caso contrário, o Mypy não verificaria o código dentro do método.
__str__
ou __repr__
, herdados de object
, por uma implementação que não funcione.
None
como default. Evitar valores nulos é uma 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 "Zero values" ("Valores zero") (EN) no Tour of Go ("Tour do Go") online
callable
deveria se tornar adequado para dicas de tipo. Em 6 de maio de 2021, quando essa nota foi escrita, essa ainda era uma questão aberta (EN).
Ellipsis
é um valor sentinela conveniente e seguro. Ele existe no Python há muito tempo, mas recentemente mais usos tem sido encontrados para ele, como vemos nas dicas de tipo e no NumPy.
import
em Java, que é apenas uma declaração para informar o compilador que determinados pacotes são necessários.
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 "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.
LineItem
usavam uma metaclasse apenas para definir o nome do armazenamento dos atributos. Veja o código nas metaclasses do exemplo da comida a granel, no repositório de código da primeira edição.