Quais são alguns casos de uso (concretos) para metaclasses?

118

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)

Ali Afshar
fonte
3
Esta é uma grande pergunta. A julgar pelas respostas abaixo, é bastante claro que não existe um uso concreto para metaclasses.
Marcus Ottosson

Respostas:

25

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:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

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:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

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.

Matt
fonte
103

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:

  1. Registro (adicionando uma classe a uma estrutura de dados):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Sempre que você cria uma subclasse Model, sua classe é registrada no modelsdicionário:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    Isso também pode ser feito com decoradores de classe:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    Ou com uma função de registro explícita:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

    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.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Refatoração (modificação de atributos de classe ou adição de novos):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Sempre que você cria uma subclasse Modele define alguns Fieldatributos, eles são injetados com seus nomes (para mensagens de erro mais informativas, por exemplo) e agrupados em um _fieldsdicioná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):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    Novamente, isso pode ser feito (sem herança) com um decorador de classe:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    Ou explicitamente:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    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:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

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.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

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 um dict, suportando assim atributos ordenados, atributos sobrecarregados e outras coisas legais perversas:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

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:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

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 Nonefor um valor válido para x?

Esta é uma maneira criativa de resolver o primeiro problema:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

E aqui está uma maneira criativa de resolver o segundo:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

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 implicitamentethisaos 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ê.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

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 .

Dan Gittik
fonte
5
Essa é uma ótima resposta, obrigado pelo tempo escrevendo e dando vários exemplos
Chen A.
"... a vantagem das metaclasses neste caso é a herança, já que funcionam para quaisquer subclasses" - não no Python 3, suponho? Acho que funciona no Python 2 apenas porque qualquer classe filha herda o __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 ?
ForceBru 05 de
2
Isso também é verdadeiro para Python 3, porque uma classe B, herdada de A, cuja metaclasse é M, também é um tipo de M. Portanto, quando B é avaliado, M é invocado para criá-lo, e isso efetivamente permite que você para "trabalhar em quaisquer subclasses" (de A). Dito isso, o Python 3.6 introduziu o muito mais simples 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.
Dan Gittik
Isso é brilhante, eu li tantos posts de blog sobre metaclasses, só que este aqui mostra os prós e contras e as alternativas à metaclasse.
ospider
36

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:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    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_fooe _set_fooconvertê-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.

Brian
fonte
3
Bem, sim, rastreando subclasses. Mas por que você iria querer isso? Seu exemplo é apenas implícito para register_music_file (Mp3File, ['.mp3']), e a forma explícita é mais legível e sustentável. Este é um exemplo dos casos graves de que estou falando.
Ali Afshar
Sobre o caso ORM, você está falando sobre a maneira baseada em classes de definir tabelas, ou as metaclasses em objetos mapeados. Porque SQLAlchemy pode (corretamente) mapear para qualquer classe (e estou assumindo que ele não usa uma metaclasse para essa atividade).
Ali Afshar
10
Eu prefiro o estilo mais declarativo, em vez de exigir métodos de registro extras para cada subclasse - melhor se tudo estiver agrupado em um único local.
Brian
Para sqlalchemy, estou pensando principalmente na camada declarativa, então talvez sqlobject seja um exemplo melhor. No entanto, as metaclasses usadas internamente também são exemplos de reinterpretação semelhante de atributos específicos para declarar significado.
Brian
2
Desculpe, um dos meus comentários se perdeu no cenário de tempo limite do SO. Acho que as classes declarativas são quase uma abominação. Sei que as pessoas adoram e é um comportamento aceito. Mas (por experiência própria) eu sei que é inutilizável em uma situação onde você deseja declarar coisas pela ONU. Cancelar o registro de uma classe é difícil .
Ali Afshar
17

Vamos começar com a citação clássica de Tim Peter:

Metaclasses são mágicas mais profundas do que 99% dos usuários deveriam se preocupar. Se você se pergunta se precisa deles, não precisa (as pessoas que realmente precisam deles sabem com certeza que precisam deles e não precisam de uma explicação sobre o motivo). Tim Peters (clp post 2002-12-22)

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.

Peter Rowell
fonte
Bem, sim, entendo o ponto. Não me pergunto "como" ou "por que" usar metaclasses, me pergunto o "quem" e o "o quê". ORMs são um caso comum aqui eu vejo. Infelizmente, o ORM do Django é muito pobre comparado ao SQLAlchemy, que tem menos magia. Magia é ruim, e metaclasses realmente não são necessárias para isso.
Ali Afshar
9
Tendo lido a citação de Tim Peters no passado, o tempo mostrou que sua declaração é bastante inútil. Só depois de pesquisar metaclasses Python aqui no StackOverflow é que se tornou aparente como implementá-las. Depois de me forçar a aprender como escrever e usar metaclasses, suas habilidades me surpreenderam e me deram uma compreensão muito melhor de como o Python realmente funciona. As classes podem fornecer código reutilizável e as metaclasses podem fornecer aprimoramentos reutilizáveis ​​para essas classes.
Noctis Skytower
6

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:

As metaclasses que usei foram principalmente para oferecer suporte a um tipo de estilo declarativo de programação. Por exemplo, considere um esquema de validação:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

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.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

Não é perfeito, mas já existe quase zero de magia, nenhuma necessidade de metaclasses e uniformidade aprimorada.

jfs
fonte
Obrigado por isso. Este é um ótimo exemplo de caso de uso que considero desnecessário, feio e não gerenciável, que seria mais simples com base em uma instância de coleção simples (com coleções aninhadas conforme necessário).
Ali Afshar
1
@Ali A: você está convidado a fornecer um exemplo concreto de comparação lado a lado entre a sintaxe declarativa por meio de metaclasses e uma abordagem baseada em instância de coleção simples.
jfs
@Ali R: você pode editar minha resposta no local para adicionar um exemplo de estilo de coleção.
jfs
Ok, fiz isso. Desculpe, estou com um pouco de pressa hoje, mas tentarei responder a qualquer dúvida amanhã / tarde. Boas festas!
Ali Afshar
2
O segundo exemplo é feio, pois você teve que vincular a instância do validador com seu nome. Uma maneira um pouco melhor de fazer isso é usar um dicionário em vez de uma lista, mas, em Python, as classes são apenas açúcar de sintaxe para o dicionário, então por que não usar classes? Você obtém validação de nome gratuita também porque python babes não pode conter espaços ou caracteres especiais que uma string poderia.
Lie Ryan
6

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 especial defe classinstruções normais e no corpo de definição de classe.

user412090
fonte
5

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:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

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.

Tríptico
fonte
2
Você pode gerar tipos dinamicamente sem usar metaclasses. >>> ajuda (tipo)
Ali Afshar
8
Mesmo se você não estiver ciente disso, estará usando metaclasses. tipo é uma metaclasse, na verdade o mais comum. :-)
Veky
5

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

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

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.

David Raznick
fonte
5

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.

Charlie Martin
fonte
1
Você também não precisa de objetos para fazer programação orientada a objetos - você poderia fazer isso com funções de primeira classe. Portanto, você não precisa usar objetos. Mas eles estão lá por conveniência. Portanto, não tenho certeza de que ponto você está tentando fazer no primeiro parágrafo.
Tyler Crompton
1
Reveja a questão.
Charlie Martin
4

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 threadboxdisso é 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?

Noctis Skytower
fonte
4

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)

Mike A
fonte
7
Não tenho certeza se a motivação era a mesma em Django.
Ali Afshar,
3

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

Benjamin Peterson
fonte
Bem, na minha opinião, o caso dos Pigmentos é simplesmente desnecessário. Por que não ter apenas uma coleção simples como um dicionário, por que forçar uma classe a fazer isso?
Ali Afshar
4
Porque uma classe legal encapula a ideia de Lexer e tem outros métodos úteis como guess_filename (), etc.
Benjamin Peterson
3

A maneira como usei metaclasses foi para fornecer alguns atributos às classes. Considere por exemplo:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

irá colocar o atributo name em cada classe que terá a metaclasse configurada para apontar para NameClass.

hiperboreiano
fonte
5
Sim, funciona. Você também pode usar uma superclasse, que é pelo menos explícita e pode ser seguida no código. Sem interesse, para que você usou isso?
Ali Afshar
2

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 __superatributo para cada classe e instância.

Eli Collins
fonte
1

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.p012017sintaxe, 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 SQLAlchemy DeclarativeMetapara iterar por meio de um intervalo de as colunas e criar automaticamente os campos do modelo correspondentes às colunas:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Eu poderia então usar essa metaclasse para a minha definição de modelo e acessar os campos enumerados automaticamente no modelo:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...
Geoffrey Hing
fonte
1

Parece haver um uso legítimo descrito aqui - Reescrever Python Docstrings com uma Metaclass.

mistermarko
fonte
0

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.

GeeF
fonte
0

a resposta de @Dan Gittik é legal

os exemplos no final podem esclarecer muitas coisas, mudei para python 3 e dou algumas explicações:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • tudo é objeto, então classe é objeto
  • objeto de classe é criado por metaclasse
  • toda classe herdada do tipo é metaclasse
  • metaclasse poderia controlar a criação de classes
  • metaclasse pode controlar a criação de metaclasse também (para que possa fazer um loop para sempre)
  • isso é metaprogramação ... você pode controlar o sistema de tipos em tempo de execução
  • novamente, tudo é objeto, este é um sistema uniforme, tipo criar tipo e tipo criar instância
gaiola
fonte
0

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.

mantenha vivo
fonte