Tenho um amigo que gosta de usar metaclasses e regularmente as oferece como solução.
Penso que quase nunca é necessário usar metaclasses. Por quê? porque eu acho que se você está fazendo algo assim para uma classe, provavelmente deveria estar fazendo para um objeto. E um pequeno redesenho / refatoração está em ordem.
Ser capaz de usar metaclasses fez com que muitas pessoas em muitos lugares usassem classes como algum tipo de objeto de segunda categoria, o que parece desastroso para mim. A programação deve ser substituída por metaprogramação? O acréscimo de decoradores de classe infelizmente o tornou ainda mais aceitável.
Então, por favor, estou desesperado para saber seus casos de uso válidos (concretos) para metaclasses em Python. Ou para ficar esclarecido sobre por que classes mutantes são melhores do que objetos mutantes, às vezes.
Vou começar:
Às vezes, ao usar uma biblioteca de terceiros, é útil poder alterar a classe de uma determinada maneira.
(Este é o único caso em que consigo pensar, e não é concreto)
Respostas:
Eu tenho uma classe que lida com plotagem não interativa, como um frontend para Matplotlib. No entanto, às vezes alguém quer fazer plotagem interativa. Com apenas algumas funções, descobri que era capaz de incrementar a contagem de figuras, chamar desenhar manualmente, etc., mas precisava fazer isso antes e depois de cada chamada de plotagem. Portanto, para criar um wrapper de plotagem interativo e um wrapper de plotagem fora da tela, descobri que era mais eficiente fazer isso por meio de metaclasses, envolvendo os métodos apropriados, do que fazer algo como:
Este método não acompanha as mudanças da API e assim por diante, mas aquele que itera sobre os atributos da classe
__init__
antes de redefinir os atributos da classe é mais eficiente e mantém as coisas atualizadas:Claro, pode haver maneiras melhores de fazer isso, mas descobri que isso é eficaz. Claro, isso também pode ser feito em
__new__
ou__init__
, mas essa foi a solução que achei mais direta.fonte
A mesma pergunta me foi feita recentemente e encontrei várias respostas. Espero que não haja problema em reviver este tópico, pois gostaria de elaborar alguns dos casos de uso mencionados e adicionar alguns novos.
A maioria das metaclasses que vi fazer uma de duas coisas:
Registro (adicionando uma classe a uma estrutura de dados):
Sempre que você cria uma subclasse
Model
, sua classe é registrada nomodels
dicionário:Isso também pode ser feito com decoradores de classe:
Ou com uma função de registro explícita:
Na verdade, isso é praticamente o mesmo: você menciona decoradores de classe de maneira desfavorável, mas na verdade não é nada mais do que um açúcar sintático para uma invocação de função em uma classe, então não há mágica nisso.
De qualquer forma, a vantagem das metaclasses neste caso é a herança, pois elas funcionam para quaisquer subclasses, enquanto as outras soluções só funcionam para subclasses explicitamente decoradas ou registradas.
Refatoração (modificação de atributos de classe ou adição de novos):
Sempre que você cria uma subclasse
Model
e define algunsField
atributos, eles são injetados com seus nomes (para mensagens de erro mais informativas, por exemplo) e agrupados em um_fields
dicionário (para facilitar a iteração, sem ter que examinar todos os atributos da classe e todas as suas classes básicas ' atributos todas as vezes):Novamente, isso pode ser feito (sem herança) com um decorador de classe:
Ou explicitamente:
Embora, ao contrário de sua defesa de não metaprogramação legível e sustentável, isso é muito mais complicado, redundante e sujeito a erros:
Tendo considerado os casos de uso mais comuns e concretos, os únicos casos em que você TEM absolutamente que usar metaclasses são quando você deseja modificar o nome da classe ou a lista de classes base, porque uma vez definidos, esses parâmetros são incorporados à classe, e nenhum decorador ou função pode desfazê-los.
Isso pode ser útil em estruturas para emitir avisos sempre que classes com nomes semelhantes ou árvores de herança incompletas são definidas, mas não consigo pensar em uma razão além de trollagem para realmente alterar esses valores. Talvez David Beazley possa.
De qualquer forma, no Python 3, as metaclasses também têm o
__prepare__
método, que permite avaliar o corpo da classe em um mapeamento diferente de umdict
, suportando assim atributos ordenados, atributos sobrecarregados e outras coisas legais perversas:Você pode argumentar que os atributos ordenados podem ser obtidos com contadores de criação e a sobrecarga pode ser simulada com argumentos padrão:
Além de ser muito mais feio, também é menos flexível: e se você quiser atributos literais ordenados, como inteiros e strings? E se
None
for um valor válido parax
?Esta é uma maneira criativa de resolver o primeiro problema:
E aqui está uma maneira criativa de resolver o segundo:
Mas isso é muito, MUITO vodu-er do que uma simples metaclasse (especialmente a primeira, que realmente derrete seu cérebro). Meu ponto é, você vê as metaclasses como estranhas e contra-intuitivas, mas você também pode vê-las como o próximo passo da evolução nas linguagens de programação: você só precisa ajustar sua mentalidade. Afinal, você provavelmente poderia fazer tudo em C, incluindo definir uma estrutura com ponteiros de função e passá-la como o primeiro argumento para suas funções. Uma pessoa vendo C ++ pela primeira vez pode dizer: "o que é essa mágica? Por que o compilador está passando implicitamente
this
aos métodos, mas não às funções regulares e estáticas? É melhor ser explícito e prolixo sobre seus argumentos ". Mas então, a programação orientada a objetos é muito mais poderosa quando você a entende; e isso é, uh ... programação quase orientada a aspectos, eu acho. E assim que você entender metaclasses, elas são realmente muito simples, então por que não usá-las quando for conveniente?E, finalmente, as metaclasses são radicais e a programação deve ser divertida. Usar construções de programação padrão e padrões de projeto o tempo todo é enfadonho e pouco inspirador e atrapalha sua imaginação. Viva um pouco! Aqui está uma metametaclasse, só para você.
Editar
Esta é uma pergunta muito antiga, mas ainda está recebendo votos positivos, então pensei em adicionar um link para uma resposta mais abrangente. Se você gostaria de ler mais sobre metaclasses e seus usos, acabei de publicar um artigo sobre isso aqui .
fonte
__metaclass__
atributo, mas esse atributo não é mais especial no Python 3. Existe alguma maneira de fazer com que essa coisa de "classes filhas também são construídas pela metaclasse do pai" funcione no Python 3 ?init_subclass
, então agora você pode manipular subclasses em uma classe base e não precisa mais de uma metaclasse para esse propósito.O propósito das metaclasses não é substituir a distinção classe / objeto por metaclasse / classe - é mudar o comportamento das definições de classe (e, portanto, suas instâncias) de alguma forma. Efetivamente, é para alterar o comportamento da instrução de classe de maneiras que podem ser mais úteis para seu domínio específico do que o padrão. As coisas para as quais eu os usei são:
Subclasses de rastreamento, geralmente para registrar manipuladores. Isso é útil ao usar uma configuração de estilo de plug-in, onde você deseja registrar um manipulador para uma coisa específica simplesmente criando uma subclasse e configurando alguns atributos de classe. por exemplo. suponha que você escreva um manipulador para vários formatos de música, onde cada classe implementa métodos apropriados (reproduzir / obter tags etc.) para seu tipo. Adicionar um manipulador para um novo tipo torna-se:
A metaclasse, então, mantém um dicionário de
{'.mp3' : MP3File, ... }
etc, e constrói um objeto do tipo apropriado quando você solicita um manipulador por meio de uma função de fábrica.Mudança de comportamento. Você pode querer atribuir um significado especial a certos atributos, resultando em comportamento alterado quando eles estão presentes. Por exemplo, você pode querer procurar métodos com o nome
_get_foo
e_set_foo
convertê-los em propriedades de forma transparente. Como um exemplo do mundo real, aqui está uma receita que escrevi para fornecer definições de struct mais semelhantes ao C. A metaclasse é usada para converter os itens declarados em uma string de formato de estrutura, manipulando herança, etc., e produzir uma classe capaz de lidar com ela.Para outros exemplos do mundo real, dê uma olhada em vários ORMs, como ORM de sqlalchemy ou sqlobject . Novamente, o objetivo é interpretar definições (aqui definições de coluna SQL) com um significado particular.
fonte
Vamos começar com a citação clássica de Tim Peter:
Dito isso, tenho (periodicamente) encontrado usos verdadeiros de metaclasses. O que vem à mente é no Django, onde todos os seus modelos herdam de modelos.Modelo. models.Model, por sua vez, faz uma mágica séria para envolver seus modelos de banco de dados com as qualidades ORM do Django. Essa mágica acontece por meio de metaclasses. Ele cria todos os tipos de classes de exceção, classes de gerenciador, etc. etc.
Veja django / db / models / base.py, class ModelBase () para o início da história.
fonte
Metaclasses podem ser úteis para a construção de linguagens específicas de domínio em Python. Exemplos concretos são Django, a sintaxe declarativa de SQLObject de esquemas de banco de dados.
Um exemplo básico de A Conservative Metaclass de Ian Bicking:
Algumas outras técnicas: Ingredientes para construir uma DSL em Python (pdf).
Editar (por Ali): Um exemplo de como fazer isso usando coleções e instâncias é o que eu preferiria. O fato importante são as instâncias, que fornecem mais potência e eliminam a razão de usar metaclasses. Além disso, vale a pena observar que seu exemplo usa uma mistura de classes e instâncias, o que certamente é uma indicação de que você não pode simplesmente fazer tudo com metaclasses. E cria uma maneira verdadeiramente não uniforme de fazer isso.
Não é perfeito, mas já existe quase zero de magia, nenhuma necessidade de metaclasses e uniformidade aprimorada.
fonte
Um padrão razoável de uso de metaclasse é fazer algo uma vez quando uma classe é definida, em vez de repetidamente sempre que a mesma classe é instanciada.
Quando várias classes compartilham o mesmo comportamento especial, repetir
__metaclass__=X
é obviamente melhor do que repetir o código de propósito especial e / ou introduzir superclasses compartilhadas ad-hoc.Mas mesmo com apenas uma classe especial e nenhuma extensão previsível,
__new__
e__init__
de uma metaclasse são uma maneira mais limpa de inicializar variáveis de classe ou outros dados globais do que misturar código de propósito especialdef
eclass
instruções normais e no corpo de definição de classe.fonte
A única vez que usei metaclasses em Python foi ao escrever um wrapper para a API do Flickr.
Meu objetivo era extrair o site da API do flickr e gerar dinamicamente uma hierarquia de classes completa para permitir o acesso à API usando objetos Python:
Nesse exemplo, como eu gerei toda a API Python Flickr do site, realmente não sei as definições de classe em tempo de execução. Ser capaz de gerar tipos dinamicamente foi muito útil.
fonte
Eu estava pensando a mesma coisa ontem e concordo totalmente. As complicações no código causadas por tentativas de torná-lo mais declarativo geralmente tornam a base de código mais difícil de manter, mais difícil de ler e menos pythônica na minha opinião. Também normalmente requer muito copy.copy () ing (para manter a herança e copiar de classe para instância) e significa que você tem que olhar em muitos lugares para ver o que está acontecendo (sempre olhando da metaclasse para cima) que vai contra o grãos de python também. Eu tenho pesquisado o formencode e o código sqlalchemy para ver se esse estilo declarativo valeu a pena e claramente não. Esse estilo deve ser deixado para descritores (como propriedade e métodos) e dados imutáveis. Ruby tem um suporte melhor para esses estilos declarativos e estou feliz que a linguagem python principal não esteja seguindo esse caminho.
Eu posso ver seu uso para depuração, adicionar uma metaclasse a todas as suas classes base para obter informações mais ricas. Também vejo seu uso apenas em projetos (muito) grandes para se livrar de algum código clichê (mas com perda de clareza). sqlalchemy, por exemplo , os usa em outro lugar, para adicionar um método personalizado específico a todas as subclasses com base em um valor de atributo em sua definição de classe, por exemplo, um exemplo de brinquedo
poderia ter uma metaclasse que gerou um método nessa classe com propriedades especiais baseadas em "hello" (digamos um método que adicionou "hello" ao final de uma string). Pode ser bom para a manutenção ter certeza de que você não precisa escrever um método em cada subclasse que você fizer, ao invés disso, tudo que você precisa definir é method_maker_value.
A necessidade disso é tão rara e apenas reduz um pouco a digitação que não vale a pena considerar, a menos que você tenha uma base de código grande o suficiente.
fonte
Você nunca precisa absolutamente usar uma metaclasse, uma vez que você sempre pode construir uma classe que faça o que você deseja usando a herança ou agregação da classe que deseja modificar.
Dito isso, pode ser muito útil em Smalltalk e Ruby ser capaz de modificar uma classe existente, mas Python não gosta de fazer isso diretamente.
Há um excelente artigo do DeveloperWorks sobre metaclasse em Python que pode ajudar. O artigo da Wikipedia também é muito bom.
fonte
Algumas bibliotecas GUI têm problemas quando vários threads tentam interagir com elas.
tkinter
é um exemplo; e embora seja possível lidar explicitamente com o problema com eventos e filas, pode ser muito mais simples usar a biblioteca de uma maneira que ignore o problema por completo. Veja - a magia das metaclasses.Ser capaz de reescrever dinamicamente uma biblioteca inteira sem problemas para que funcione corretamente como esperado em um aplicativo multithread pode ser extremamente útil em algumas circunstâncias. O módulo safetkinter faz isso com a ajuda de uma metaclasse fornecida pelo módulo threadbox - eventos e filas não são necessários.
Um aspecto interessante
threadbox
disso é que ele não se importa com a classe que clona. Ele fornece um exemplo de como todas as classes básicas podem ser tocadas por uma metaclasse, se necessário. Um outro benefício que vem com as metaclasses é que elas também são executadas em classes herdadas. Programas que se escrevem - por que não?fonte
O único caso de uso legítimo de uma metaclasse é impedir que outros desenvolvedores intrometidos mexam no seu código. Assim que um desenvolvedor intrometido dominar as metaclasses e começar a mexer nas suas, acrescente outro nível ou dois para mantê-los fora. Se isso não funcionar, comece a usar
type.__new__
ou talvez algum esquema usando uma metaclasse recursiva.(escrito com ironia, mas já vi esse tipo de ofuscação ser feito. Django é um exemplo perfeito)
fonte
Metaclasses não estão substituindo a programação! Eles são apenas um truque que pode automatizar ou tornar mais elegantes algumas tarefas. Um bom exemplo disso é a biblioteca de realce de sintaxe de Pigmentos . Ele tem uma classe chamada
RegexLexer
que permite ao usuário definir um conjunto de regras lexing como expressões regulares em uma classe. Uma metaclasse é usada para transformar as definições em um analisador útil.Eles são como sal; é muito fácil de usar.
fonte
A maneira como usei metaclasses foi para fornecer alguns atributos às classes. Considere por exemplo:
irá colocar o atributo name em cada classe que terá a metaclasse configurada para apontar para NameClass.
fonte
Este é um uso secundário, mas ... uma coisa para a qual considero as metaclasses úteis é para invocar uma função sempre que uma subclasse é criada. Codifiquei isso em uma metaclasse que procura por um
__initsubclass__
atributo: sempre que uma subclasse é criada, todas as classes pai que definem esse método são chamadas com__initsubclass__(cls, subcls)
. Isso permite a criação de uma classe pai que, em seguida, registra todas as subclasses com algum registro global, executa verificações invariáveis nas subclasses sempre que são definidas, realiza operações de ligação tardia, etc ... tudo sem ter que chamar funções manualmente ou criar metaclasses personalizadas que desempenhar cada uma dessas funções separadas.Veja bem, aos poucos comecei a perceber que a magia implícita desse comportamento é um tanto indesejável, já que é inesperado se olharmos para uma definição de classe fora do contexto ... então deixei de usar essa solução para qualquer coisa séria inicializando um
__super
atributo para cada classe e instância.fonte
Recentemente, tive que usar uma metaclasse para ajudar a definir declarativamente um modelo SQLAlchemy em torno de uma tabela de banco de dados preenchida com dados do Censo dos EUA de http://census.ire.org/data/bulkdata.html
IRE fornece shells de banco de dados para as tabelas de dados do censo, que criam colunas inteiras seguindo uma convenção de nomenclatura do Census Bureau de p012015, p012016, p012017, etc.
Eu queria a) ser capaz de acessar essas colunas usando uma
model_instance.p012017
sintaxe, b) ser bastante explícito sobre o que estava fazendo ec) não precisar definir explicitamente dezenas de campos no modelo, então criei uma subclasse de SQLAlchemyDeclarativeMeta
para iterar por meio de um intervalo de as colunas e criar automaticamente os campos do modelo correspondentes às colunas:Eu poderia então usar essa metaclasse para a minha definição de modelo e acessar os campos enumerados automaticamente no modelo:
fonte
Parece haver um uso legítimo descrito aqui - Reescrever Python Docstrings com uma Metaclass.
fonte
Tive que usá-los uma vez para um analisador binário para torná-lo mais fácil de usar. Você define uma classe de mensagem com atributos dos campos presentes na transmissão. Eles precisavam ser ordenados da maneira como foram declarados para construir o formato final do fio a partir dele. Você pode fazer isso com metaclasses, se usar um dicionário de namespace ordenado. Na verdade, está nos exemplos para Metaclasses:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
Mas em geral: avalie com muito cuidado se você realmente precisa da complexidade adicional das metaclasses.
fonte
a resposta de @Dan Gittik é legal
os exemplos no final podem esclarecer muitas coisas, mudei para python 3 e dou algumas explicações:
fonte
Outro caso de uso é quando você deseja modificar atributos de nível de classe e ter certeza de que isso afeta apenas o objeto em questão. Na prática, isso implica "mesclar" as fases de metaclasses e instanciações de classes, levando você a lidar apenas com as instâncias de classe de seu próprio tipo (exclusivo).
Eu também tive que fazer isso quando (por questões de legibilidade e polimorfismo ) queríamos definir dinamicamente
property
s cujos valores retornados (podem) resultar de cálculos baseados em atributos de nível de instância (frequentemente alterados), o que só pode ser feito no nível de classe , ou seja , após a instanciação da metaclasse e antes da instanciação da classe.fonte