oop java - O que são metaclasses em Python?



python-datamodel (13)

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

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


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


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__

Função de um __call__()método de metaclasse ao criar uma instância de classe

Se você fez programação em Python por mais de alguns meses, eventualmente encontrará um código como este:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

O último é possível quando você implementa o __call__()método mágico na classe.

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

O __call__()método é invocado quando uma instância de uma classe é usada como uma chamada. Mas, como vimos nas respostas anteriores, uma classe em si é uma instância de uma metaclasse, portanto, quando usamos a classe como uma chamada (ou seja, quando criamos uma instância dela), estamos na verdade chamando seu __call__()método de metaclasse . Nesse ponto, a maioria dos programadores de Python está um pouco confusa, porque disseram que, ao criar uma instância como essa, instance = SomeClass()você está chamando seu __init__()método. Alguns que já cavou um pouco mais profundo saber que antes __init__()não __new__(). Bem, hoje outra camada da verdade está sendo revelada, antes que __new__()haja a metaclasse ” __call__().

Vamos estudar a cadeia de chamadas de método especificamente da perspectiva de criar uma instância de uma classe.

Essa é uma metaclasse que registra exatamente o momento antes de uma instância ser criada e no momento em que está prestes a retorná-la.

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

Esta é uma classe que usa essa metaclasse

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

E agora vamos criar uma instância de Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

Observe que o código acima não faz nada além de registrar as tarefas. Cada método delega o trabalho real à implementação de seu pai, mantendo assim o comportamento padrão. Desde typeseja Meta_1classe dos pais ( typesendo o metaclass pai padrão) e considerando a seqüência de ordenação da saída acima, agora temos uma pista sobre o que seria a implementação pseudo de type.__call__():

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

Podemos ver que o __call__()método da metaclasse é aquele que é chamado primeiro. Em seguida, ele delega a criação da instância para o __new__()método e a inicialização da classe para a instância __init__(). É também o que retorna a instância.

Do exposto resulta que a metaclass' __call__()também é dada a oportunidade de decidir se quer ou não uma chamada para Class_1.__new__()ou Class_1.__init__()acabará por ser feita. Ao longo de sua execução, ele poderia retornar um objeto que não tenha sido tocado por nenhum desses métodos. Tomemos por exemplo essa abordagem para o padrão singleton:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

Vamos observar o que acontece quando tentamos repetidamente criar um objeto do tipo Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True

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.


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()

Além das respostas publicadas, posso dizer que metaclassdefine o comportamento de uma classe. Então, você pode definir explicitamente sua metaclasse. Sempre que o Python obtém uma palavra class- chave , ela começa a pesquisar pelo metaclass. Se não for encontrado - o tipo de metaclasse padrão é usado para criar o objeto da classe. Usando o __metaclass__atributo, você pode definir metaclasssua classe:

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

Isso produzirá a saída assim:

class 'type'

E, claro, você pode criar o seu próprio metaclasspara definir o comportamento de qualquer classe criada usando sua classe.

Para fazer isso, sua metaclassclasse de tipo padrão deve ser herdada, pois esta é a principal metaclass:

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

A saída será:

class '__main__.MyMetaClass'
class 'type'

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

Outros explicaram como as metaclasses funcionam e como elas se encaixam no sistema de tipos Python. Aqui está um exemplo do que eles podem ser usados. Em uma estrutura de testes que escrevi, queria acompanhar a ordem em que as classes foram definidas, para que eu pudesse instanciá-las posteriormente nessa ordem. Eu achei mais fácil fazer isso usando uma metaclasse.

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

Qualquer coisa que seja uma subclasse de MyType obtém um atributo de classe _order que registra a ordem na qual as classes foram definidas.


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.


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

typeé na verdade uma metaclass- uma classe que cria outras classes. A maioria metaclasssão subclasses de type. O metaclassrecebe a newclasse como seu primeiro argumento e fornece acesso ao objeto de classe com detalhes conforme mencionado abaixo:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

Observe que a classe não foi instanciada a qualquer momento; o simples ato de criar a classe desencadeou a execução do metaclass.


Um rápido avanço de outros métodos idênticos no iPython revela que @staticmethodgera ganhos marginais de desempenho (nos nanossegundos), mas, de outro modo, parece não ter função alguma. Além disso, quaisquer ganhos de desempenho provavelmente serão eliminados pelo trabalho adicional de processamento do método staticmethod()durante a compilação (o que acontece antes de qualquer execução de código quando você executa um script).

Por uma questão de legibilidade do código, eu evitaria, a @staticmethodmenos que seu método fosse usado para cargas de trabalho, onde os nanossegundos contam.





python oop metaclass python-datamodel