O que são metaclasses em Python?




metaclasse java (10)

O que são metaclasses e para que os usamos?

https://code.i-harness.com


O que são metaclasses? Pra quê você usa eles?

TLDR: Uma metaclasse instancia e define o comportamento de uma classe como uma classe instancia e define o comportamento de uma instância.

Pseudo-código:

>>> Class(...)
instance

O acima deve parecer familiar. Bem, de onde vem a Class ? É uma instância de uma metaclasse (também pseudocódigo):

>>> Metaclass(...)
Class

No código real, podemos passar a metaclasse padrão, type , tudo o que precisamos para instanciar uma classe e obtemos uma classe:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Colocando de forma diferente

  • Uma classe é para uma instância como uma metaclasse é para uma classe.

    Quando instanciamos um objeto, obtemos uma instância:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    Da mesma forma, quando definimos uma classe explicitamente com a metaclasse padrão type , nós a instanciamos:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Em outras palavras, uma classe é uma instância de uma metaclasse:

    >>> isinstance(object, type)
    True
    
  • Coloque uma terceira maneira, uma metaclasse é uma classe de classe.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

Quando você escreve uma definição de classe eo Python a executa, ela usa uma metaclasse para instanciar o objeto de classe (que, por sua vez, será usado para instanciar instâncias dessa classe).

Assim como podemos usar definições de classe para alterar o comportamento de instâncias de objetos personalizados, podemos usar uma definição de classe de metaclasse para alterar a maneira como um objeto de classe se comporta.

Para que eles podem ser usados? Dos docs :

Os usos potenciais para metaclasses são ilimitados. Algumas ideias exploradas incluem criação de log, verificação de interface, delegação automática, criação automática de propriedade, proxies, estruturas e bloqueio / sincronização automática de recursos.

No entanto, geralmente é recomendável que os usuários evitem usar metaclasses, a menos que seja absolutamente necessário.

Você usa uma metaclasse toda vez que cria uma classe:

Quando você escreve uma definição de classe, por exemplo, assim,

class Foo(object): 
    'demo'

Você instancia um objeto de classe.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

É o mesmo que chamar funcionalmente com os argumentos apropriados e atribuir o resultado a uma variável desse nome:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Note que algumas coisas são automaticamente adicionadas ao __dict__ , isto é, o namespace:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

A metaclasse do objeto que criamos, em ambos os casos, é type .

(Uma nota sobre o conteúdo da classe __dict__ : __module__ está lá porque as classes devem saber onde estão definidas, e __dict__ e __weakref__ estão lá porque nós não definimos __slots__ - se definirmos __slots__ , salvaremos um pouco de espaço nas instâncias, como podemos desaprovar __dict__ e __weakref__ excluindo-os. Por exemplo:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... mas eu discordo.)

Podemos estender o type como qualquer outra definição de classe:

Aqui está o padrão __repr__ das classes:

>>> Foo
<class '__main__.Foo'>

Uma das coisas mais valiosas que podemos fazer por padrão ao escrever um objeto Python é prover um bom resultado __repr__. Quando ligamos help(repr), aprendemos que há um bom teste para um __repr__que também requer um teste de igualdade - obj == eval(repr(obj)). A seguinte implementação simples de __repr__e __eq__para instâncias de classe de nossa classe de tipo nos fornece uma demonstração que pode melhorar o padrão __repr__de classes:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Então, agora, quando criamos um objeto com essa metaclasse, o __repr__eco na linha de comando fornece uma visão muito menos feia do que o padrão:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

Com um bom __repr__definido para a instância da classe, temos uma capacidade mais forte de depurar nosso código. No entanto, é muito eval(repr(Class))improvável que seja feita uma verificação muito maior (como seria bastante impossível avaliar as funções de seus padrões __repr__).

Um uso esperado: __prepare__um namespace

Se, por exemplo, queremos saber em que ordem os métodos de uma classe são criados, poderíamos fornecer um dict ordenado como o namespace da classe. Nós faríamos isso com o __prepare__qual retorna o namespace dict para a classe se ela for implementada no Python 3 :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

E uso:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

E agora temos um registro da ordem em que esses métodos (e outros atributos de classe) foram criados:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Note que este exemplo foi adaptado da docs - o novo enum na biblioteca padrão faz isso.

Então, o que fizemos foi instanciar uma metaclasse criando uma classe. Também podemos tratar a metaclasse como faríamos com qualquer outra classe. Tem uma ordem de resolução de método:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

E tem aproximadamente o correto repr(que não podemos mais avaliar a menos que possamos encontrar uma maneira de representar nossas funções):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})

Classes como objetos

Antes de entender as metaclasses, você precisa dominar as classes em Python. E o Python tem uma idéia muito peculiar de quais classes são emprestadas da linguagem Smalltalk.

Na maioria das linguagens, as classes são apenas partes do código que descrevem como produzir um objeto. Isso também é verdade no Python:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Mas as classes são mais do que isso em Python. Classes são objetos também.

Sim, objetos.

Assim que você usa a class keyword, o Python a executa e cria um OBJECT. A instrução

>>> class ObjectCreator(object):
...       pass
...

cria na memória um objeto com o nome "ObjectCreator".

Este objeto (a classe) é capaz de criar objetos (as instâncias), e é por isso que é uma classe .

Mas ainda assim, é um objeto e, portanto:

  • você pode atribuí-lo a uma variável
  • você pode copiá-lo
  • você pode adicionar atributos a ele
  • você pode passá-lo como um parâmetro de função

por exemplo:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Criando Classes Dinamicamente

Como as classes são objetos, você pode criá-las instantaneamente, como qualquer objeto.

Primeiro, você pode criar uma classe em uma função usando a class :

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Mas não é tão dinâmico, já que você ainda tem que escrever a turma toda.

Como as classes são objetos, elas devem ser geradas por alguma coisa.

Quando você usa a palavra-chave class , o Python cria esse objeto automaticamente. Mas, como acontece com a maioria das coisas em Python, ele oferece uma maneira de fazer isso manualmente.

Lembre-se do type função? A boa e antiga função que permite saber o tipo de um objeto:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Bem, o type tem uma capacidade completamente diferente, ele também pode criar classes na hora. type pode pegar a descrição de uma classe como parâmetros e retornar uma classe.

(Eu sei, é bobagem que a mesma função possa ter dois usos completamente diferentes de acordo com os parâmetros que você passa para ela. É um problema devido à compatibilidade retroativa no Python)

type funciona desta maneira:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

por exemplo:

>>> class MyShinyClass(object):
...       pass

pode ser criado manualmente desta maneira:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Você notará que usamos "MyShinyClass" como o nome da classe e como a variável para manter a referência de classe. Eles podem ser diferentes, mas não há razão para complicar as coisas.

type aceita um dicionário para definir os atributos da classe. Assim:

>>> class Foo(object):
...       bar = True

Pode ser traduzido para:

>>> Foo = type('Foo', (), {'bar':True})

E usado como uma classe normal:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

E, claro, você pode herdar dele, então:

>>>   class FooChild(Foo):
...         pass

seria:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Eventualmente, você desejará adicionar métodos à sua turma. Apenas defina uma função com a assinatura apropriada e atribua-a como um atributo.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

E você pode adicionar ainda mais métodos depois de criar dinamicamente a classe, assim como adicionar métodos a um objeto de classe normalmente criado.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Você vê para onde estamos indo: no Python, classes são objetos e você pode criar uma classe dinamicamente.

Isso é o que o Python faz quando você usa a class keyword e faz isso usando uma metaclasse.

O que são metaclasses (finalmente)

Metaclasses são o 'material' que cria classes.

Você define classes para criar objetos, certo?

Mas aprendemos que classes Python são objetos.

Bem, metaclasses são o que criam esses objetos. Eles são as classes das classes, você pode imaginá-las assim:

MyClass = MetaClass()
my_object = MyClass()

Você viu esse type permite fazer algo assim:

MyClass = type('MyClass', (), {})

É porque o type função é de fato uma metaclasse. type é o metaclasse que o Python usa para criar todas as classes nos bastidores.

Agora você se pergunta por que diabos ele está escrito em letras minúsculas e não em Type ?

Bem, eu acho que é uma questão de consistência com str , a classe que cria objetos strings, e int a classe que cria objetos inteiros. type é apenas a classe que cria objetos de classe.

Você vê isso verificando o atributo __class__ .

Tudo, e eu quero dizer tudo, é um objeto em Python. Isso inclui ints, strings, funções e classes. Todos eles são objetos. E todos eles foram criados a partir de uma aula:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Agora, qual é a __class__ de qualquer __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Então, uma metaclasse é apenas o material que cria objetos de classe.

Você pode chamá-lo de 'fábrica de classes', se desejar.

type é o metaclasse embutido que o Python usa, mas é claro, você pode criar sua própria metaclasse.

O atributo __metaclass__

No Python 2, você pode adicionar um atributo __metaclass__ ao escrever uma classe (veja a próxima seção para a sintaxe do Python 3):

class Foo(object):
    __metaclass__ = something...
    [...]

Se você fizer isso, o Python usará a metaclasse para criar a classe Foo .

Cuidado, é complicado.

Você escreve primeiro a class Foo(object) , mas o objeto de classe Foo ainda não é criado na memória.

O Python procurará __metaclass__ na definição da classe. Se ele for encontrado, ele será usado para criar a classe de objeto Foo . Caso contrário, usará o type para criar a classe.

Leia isso várias vezes.

Quando você faz:

class Foo(Bar):
    pass

O Python faz o seguinte:

Existe um atributo __metaclass__ no Foo ?

Se sim, crie na memória um objeto de classe (eu disse um objeto de classe, fique comigo aqui), com o nome Foo usando o que está em __metaclass__ .

Se o Python não puder encontrar __metaclass__ , ele procurará um __metaclass__ no nível de MODULE e tentará fazer o mesmo (mas apenas para classes que não herdam nada, basicamente classes de estilo antigo).

Então, se não puder encontrar nenhuma __metaclass__ , ela usará a própria metaclasse da Bar (o primeiro pai) (que pode ser o type padrão) para criar o objeto de classe.

Tenha cuidado aqui que o atributo __metaclass__ não será herdado, a metaclasse do pai ( Bar.__class__ ) será. Se Bar usou um atributo __metaclass__ que criou Bar com type() (e não type.__new__() ), as subclasses não herdarão esse comportamento.

Agora a grande questão é: o que você pode colocar em __metaclass__ ?

A resposta é: algo que pode criar uma classe.

E o que pode criar uma aula? type , ou qualquer coisa que subclasses ou usa.

Metaclasses em Python 3

A sintaxe para definir a metaclasse foi alterada no Python 3:

class Foo(object, metaclass=something):
    [...]

ou seja, o atributo __metaclass__ não é mais usado em favor de um argumento de palavra-chave na lista de classes base.

O comportamento das metaclasses, no entanto, permanece basicamente o mesmo .

Metaclasses personalizados

O objetivo principal de uma metaclasse é alterar a classe automaticamente, quando ela é criada.

Você geralmente faz isso para APIs, onde deseja criar classes correspondentes ao contexto atual.

Imagine um exemplo estúpido, em que você decide que todas as classes do seu módulo devem ter seus atributos escritos em letras maiúsculas. Existem várias maneiras de fazer isso, mas uma maneira é definir __metaclass__ no nível do módulo.

Desta forma, todas as classes deste módulo serão criadas usando esta metaclasse, e nós apenas temos que dizer à metaclasse para transformar todos os atributos em maiúsculos.

Felizmente, __metaclass__ pode realmente ser qualquer chamada, não precisa ser uma classe formal (eu sei, algo com 'classe' em seu nome não precisa ser uma classe, vá em frente ... mas é útil).

Então vamos começar com um exemplo simples, usando uma função.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Agora, vamos fazer exatamente o mesmo, mas usando uma classe real para uma metaclasse:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

Mas isso não é realmente OOP. Nós chamamos o type diretamente e não sobrescrevemos ou chamamos o pai __new__ . Vamos fazer isso:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

Você deve ter notado o argumento extra upperattr_metaclass . Não há nada de especial nisso: __new__ sempre recebe a classe em que está definida, como primeiro parâmetro. Assim como você tem self para métodos comuns que recebem a instância como primeiro parâmetro, ou a classe de definição para os métodos de classe.

Naturalmente, os nomes que usei aqui são longos por uma questão de clareza, mas, como para o self , todos os argumentos têm nomes convencionais. Então uma metaclasse de produção real ficaria assim:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

Nós podemos torná-lo ainda mais limpo usando super , o que irá facilitar a herança (porque sim, você pode ter metaclasses, herdando de metaclasses, herdando do tipo):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

É isso aí. Não há nada mais sobre metaclasses.

A razão por trás da complexidade do código usando metaclasses não é por causa das metaclasses, é porque você geralmente usa metaclasses para fazer coisas distorcidas confiando em introspecção, manipulando herança, vars como __dict__ , etc.

De fato, as metaclasses são especialmente úteis para fazer magia negra e, portanto, coisas complicadas. Mas por si mesmos, eles são simples:

  • interceptar uma criação de classe
  • modificar a classe
  • retornar a classe modificada

Por que você usaria classes metaclasses em vez de funções?

Como __metaclass__ pode aceitar qualquer chamada, por que você usaria uma classe, já que é obviamente mais complicado?

Existem várias razões para o fazer:

  • A intenção é clara. Quando você lê UpperAttrMetaclass(type) , você sabe o que vai seguir
  • Você pode usar o OOP. Metaclasse pode herdar da metaclasse, substituir métodos pai. Os metaclasses podem até usar metaclasses.
  • As subclasses de uma classe serão instâncias de sua metaclasse se você especificou uma classe de metaclasse, mas não com uma função de metaclasse.
  • Você pode estruturar melhor seu código. Você nunca usa metaclasses para algo tão trivial quanto o exemplo acima. Geralmente é algo complicado. Ter a capacidade de criar vários métodos e agrupá-los em uma classe é muito útil para facilitar a leitura do código.
  • Você pode ligar __new__ , __init__ e __call__ . Que permitirá que você faça coisas diferentes. Mesmo que normalmente você possa fazer tudo em __new__ , algumas pessoas ficam mais confortáveis ​​usando __init__ .
  • Estes são chamados de metaclasses, droga! Deve significar alguma coisa!

Por que você usaria metaclasses?

Agora a grande questão. Por que você usaria algum recurso obscuro propenso a erros?

Bem, normalmente você não:

Metaclasses são uma magia mais profunda que 99% dos usuários nunca devem se preocupar. Se você quer saber se você precisa deles, você não precisa (as pessoas que realmente precisam deles sabem com certeza que eles precisam deles, e não precisam de uma explicação sobre o porquê).

Guru Python Tim Peters

O principal caso de uso para uma metaclasse é a criação de uma API. Um exemplo típico disso é o ORM do Django.

Ele permite que você defina algo como isto:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Mas se você fizer isso:

guy = Person(name='bob', age='35')
print(guy.age)

Não irá retornar um objeto IntegerField . Ele retornará um int e pode até mesmo obtê-lo diretamente do banco de dados.

Isso é possível porque models.Model define __metaclass__ e usa alguma mágica que transformará a Person você acabou de definir com instruções simples em um gancho complexo em um campo de banco de dados.

O Django faz com que algo complexo pareça simples expondo uma API simples e usando metaclasses, recriando código desta API para fazer o trabalho real nos bastidores.

A última palavra

Primeiro, você sabe que classes são objetos que podem criar instâncias.

Bem, na verdade, as aulas são instâncias. De metaclasses.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Tudo é um objeto em Python e todos são instâncias de classes ou instâncias de metaclasses.

Exceto pelo type .

type é na verdade sua própria metaclasse. Isso não é algo que você poderia reproduzir em Python puro e é feito trapaceando um pouco no nível de implementação.

Em segundo lugar, as metaclasses são complicadas. Você pode não querer usá-las para alterações de classe muito simples. Você pode alterar as classes usando duas técnicas diferentes:

99% do tempo que você precisa de alteração de classe, é melhor usá-las.

Mas 98% do tempo, você não precisa de alteração de classe.


A versão dr

A type(obj)função obtém o tipo de um objeto.

O type()de uma classe é sua metaclasse .

Para usar uma metaclasse:

class Foo(object):
    __metaclass__ = MyMetaClass

Eu acho que a introdução do ONLamp à programação metaclasse está bem escrita e dá uma boa introdução ao tema, apesar de já ter vários anos de existência.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (arquivado em https://web.archive.org/web/20080206005253/http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html )

Resumindo: Uma classe é um blueprint para a criação de uma instância, uma metaclasse é um blueprint para a criação de uma classe. Pode ser facilmente visto que em classes Python também precisam ser objetos de primeira classe para habilitar esse comportamento.

Eu mesmo nunca escrevi um, mas acho que um dos usos mais legais de metaclasses pode ser visto no framework do Django . As classes de modelo usam uma abordagem de metaclasse para habilitar um estilo declarativo de escrever novos modelos ou classes de formulário. Enquanto a metaclasse está criando a classe, todos os membros têm a possibilidade de personalizar a própria classe.

O que resta a dizer é: se você não sabe o que são metaclasses, a probabilidade de não precisar deles é de 99%.


Um uso para metaclasses é adicionar novas propriedades e métodos a uma instância automaticamente.

Por exemplo, se você olhar para os modelos do Django , sua definição parece um pouco confusa. Parece que você está apenas definindo propriedades de classe:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

No entanto, em tempo de execução, os objetos Person são preenchidos com todos os tipos de métodos úteis. Veja a source de alguma metaclasse incrível.


Uma metaclasse é a classe de uma classe. Como uma classe define como uma instância da classe se comporta, uma metaclasse define como uma classe se comporta. Uma classe é uma instância de uma metaclasse.

Enquanto em Python você pode usar callables arbitrários para metaclasses (como shows de Jerub ), a abordagem mais útil é realmente torná-lo uma classe real em si. type é a metaclasse usual em Python. Caso você esteja se perguntando, sim, o type é em si uma classe e é do seu próprio tipo. Você não poderá recriar algo como o type puramente em Python, mas o Python trai um pouco. Para criar sua própria metaclasse em Python, você realmente quer apenas o type subclasse.

Uma metaclasse é mais comumente usada como uma fábrica de classes. Como você cria uma instância da classe chamando a classe, o Python cria uma nova classe (quando executa a instrução 'class') chamando a metaclasse. Combinados com os métodos __init__ e __new__ normais, as metaclasses permitem que você faça 'coisas extras' ao criar uma classe, como registrar a nova classe com algum registro, ou até mesmo substituir a classe por outra completamente diferente.

Quando a instrução de class é executada, o Python primeiro executa o corpo da instrução de class como um bloco de código normal. O namespace resultante (um dict) contém os atributos da futura classe. A metaclasse é determinada observando as classes base da classe futura (as metaclasses são herdadas), no atributo __metaclass__ da variável global __metaclass__ (se houver) ou __metaclass__ . A metaclasse é então chamada com o nome, bases e atributos da classe para instanciá-la.

No entanto, as metaclasses realmente definem o tipo de uma classe, não apenas uma fábrica, para que você possa fazer muito mais com elas. Você pode, por exemplo, definir métodos normais na metaclasse. Esses métodos metaclasse são como métodos de classe, pois podem ser chamados na classe sem uma instância, mas também não são como métodos de classe, pois não podem ser chamados em uma instância da classe. type.__subclasses__() é um exemplo de um método no type metaclasse. Você também pode definir os métodos 'mágicos' normais, como __add__ , __iter__ e __getattr__ , para implementar ou alterar o comportamento da classe.

Aqui está um exemplo agregado dos bits e peças:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

Note que esta resposta é para o Python 2.x como foi escrito em 2008, os metaclasses são ligeiramente diferentes em 3.x, veja os comentários.

Metaclasses são o molho secreto que faz o trabalho de 'classe'. A metaclasse padrão para um novo objeto de estilo é chamada de 'tipo'.

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

Metaclasses levam 3 args. ' name ', ' bases ' e ' dict '

Aqui é onde o segredo começa. Procure por onde nome, bases e o dict vêm nesta definição de classe de exemplo.

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

Vamos definir uma metaclasse que demonstrará como ' class: ' a chama.

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

E agora, um exemplo que realmente significa alguma coisa, isso automaticamente fará com que as variáveis ​​na lista "attributes" sejam definidas na classe e definidas como None.

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

Note que o comportamento mágico que 'Initalised' ganha por ter os init_attributes da metaclasse não é passado para uma subclasse de Initalised.

Aqui está um exemplo ainda mais concreto, mostrando como você pode subclassificar 'type' para fazer uma metaclasse que executa uma ação quando a classe é criada. Isso é bastante complicado:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b

Atualização do Python 3

Existem (neste ponto) dois métodos-chave em uma metaclasse:

  • __prepare__ e
  • __new__

__prepare__permite fornecer um mapeamento personalizado (como um OrderedDict) para ser usado como o namespace enquanto a classe está sendo criada. Você deve retornar uma instância do namespace que você escolher. Se você não implementar __prepare__um normal dicté usado.

__new__ é responsável pela criação / modificação real da classe final.

Uma metaclasse de esqueletos e nada extra gostaria de:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

Um exemplo simples:

Digamos que você queira que um código de validação simples seja executado em seus atributos - como sempre deve ser um intou um str. Sem uma metaclasse, sua turma seria algo como:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

Como você pode ver, você precisa repetir o nome do atributo duas vezes. Isso possibilita erros de digitação junto com bugs irritantes.

Uma metaclasse simples pode resolver esse problema:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

Isto é o que a metaclasse se pareceria (não usando __prepare__desde que não é necessário):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

Uma amostra de execução de:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

produz:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

Nota : Este exemplo é bastante simples e também poderia ter sido realizado com um decorador de classes, mas presumivelmente uma metaclasse real estaria fazendo muito mais.

A classe 'ValidateType' para referência:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value

As classes Python são elas mesmas objetos - como no caso - de sua meta-classe.

A metaclasse padrão, que é aplicada quando você determina classes como:

class foo:
    ...

A meta classe é usada para aplicar alguma regra a um conjunto inteiro de classes. Por exemplo, suponha que você esteja criando um ORM para acessar um banco de dados e queira que os registros de cada tabela sejam de uma classe mapeada para essa tabela (com base em campos, regras de negócios, etc.), um possível uso de metaclasse é, por exemplo, a lógica do conjunto de conexões, que é compartilhada por todas as classes de registro de todas as tabelas. Outro uso é lógico para suportar chaves estrangeiras, o que envolve múltiplas classes de registros.

quando você define metaclasse, você insere o tipo de subclasse e pode substituir os seguintes métodos mágicos para inserir sua lógica.

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

de qualquer forma, esses dois são os ganchos mais usados. metaclassing é poderoso, e acima está longe e lista exaustiva de usos para metaclassing.


Uma metaclasse é uma classe que informa como (algumas) outras classes devem ser criadas.

Este é um caso em que vi a metaclasse como uma solução para o meu problema: eu tinha um problema muito complicado, que provavelmente poderia ter sido resolvido de forma diferente, mas optei por resolvê-lo usando uma metaclasse. Por causa da complexidade, é um dos poucos módulos que escrevi onde os comentários no módulo ultrapassam a quantidade de código que foi escrito. Aqui está...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()




python-datamodel