O que são metaclasses em Python?

Respostas:

2869

Uma metaclasse é a classe de uma classe. Uma classe define como uma instância da classe (ou seja, um objeto) se comporta, enquanto uma metaclasse define como uma classe se comporta. Uma classe é uma instância de uma metaclasse.

Enquanto no Python você pode usar chamadas arbitrárias para metaclasses (como mostra Jerub ), a melhor abordagem é torná-la uma classe real. typeé a metaclasse usual em Python. typeé em si uma classe e é do seu próprio tipo. Você não poderá recriar algo como typepuramente no Python, mas o Python trapaceia um pouco. Para criar sua própria metaclasse no Python, você realmente quer apenas subclassificar type.

Uma metaclasse é mais comumente usada como uma fábrica de classes. Quando você cria um objeto chamando a classe, o Python cria uma nova classe (quando executa a instrução 'class') chamando a metaclasse. Combinadas com o normal __init__e os __new__métodos, as metaclasses permitem que você faça "coisas extras" ao criar uma classe, como registrar a nova classe com algum registro ou substituir a classe por algo completamente diferente.

Quando a classinstrução é executada, o Python primeiro executa o corpo da classinstrução como um bloco de código normal. O espaço para nome resultante (um dict) mantém os atributos da classe a ser. A metaclasse é determinada observando as classes básicas da classe a ser (as metaclasses são herdadas), o __metaclass__atributo da classe a ser (se houver) ou a __metaclass__variável global. A metaclasse é então chamada com o nome, bases e atributos da classe para instancia-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 de 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 método na typemetaclasse. Você também pode definir os métodos 'mágicos' normais, como __add__, __iter__e __getattr__, para implementar ou alterar como a classe se comporta.

Aqui está um exemplo agregado dos bits e partes:

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__
Thomas Wouters
fonte
13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
precisa saber é
20
ppperry, ele obviamente quis dizer que você não pode recriar o tipo sem usar o próprio tipo como uma metaclasse. O que é justo o suficiente para dizer.
Holle van
3
Unregister () não deve ser chamado pela instância da classe Example?
ciasto Piekarz
5
Observe que isso __metaclass__não é suportado no Python 3. No uso do Python 3 class MyObject(metaclass=MyType), consulte python.org/dev/peps/pep-3115 e a resposta abaixo.
BlackShift
2
A documentação descreve como a metaclasse é escolhida . A metaclasse não é herdada tanto quanto é derivada. Se você especificar uma metaclasse, ela deverá ser um subtipo de cada metaclasse da classe base; caso contrário, você usará a metaclasse da classe base que é um subtipo da outra metaclasse da classe base. Observe que é possível que nenhuma metaclasse válida possa ser encontrada e a definição falhará.
chepner 9/01
6820

Classes como objetos

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

Na maioria dos idiomas, as classes são apenas partes de 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 aulas são mais do que isso em Python. Classes também são objetos.

Sim, objetos.

Assim que você usa a palavra-chave class, 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".

Esse 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 rapidamente, como qualquer objeto.

Primeiro, você pode criar uma classe em uma função usando 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, pois você ainda precisa escrever a classe toda.

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

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

Lembra da função type? A boa e antiga função que permite que você saiba que tipo de objeto é:

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

Bem, typetem uma habilidade completamente diferente, também pode criar classes em tempo real. typepode 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 com versões anteriores no Python)

type funciona desta maneira:

type(name, bases, attrs)

Onde:

  • name: nome da classe
  • bases: tupla da classe pai (por herança, pode estar vazia)
  • attrs: dicionário contendo nomes e valores de atributos

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 da classe. Eles podem ser diferentes, mas não há razão para complicar as coisas.

typeaceita 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, portanto:

>>>   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 classe. 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 criado normalmente.

>>> 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, as classes são objetos e você pode criar uma classe dinamicamente.

Isso é o que o Python faz quando você usa a palavra-chave classe o faz usando uma metaclasse.

O que são metaclasses (finalmente)

Metaclasses são as 'coisas' que criam classes.

Você define classes para criar objetos, certo?

Mas aprendemos que as classes Python são objetos.

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

MyClass = MetaClass()
my_object = MyClass()

Você viu que isso typepermite que você faça algo assim:

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

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

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

Bem, acho que é uma questão de consistência com stra classe que cria objetos de strings e inta classe que cria objetos de número inteiro. typeé apenas a classe que cria objetos de classe.

Você vê isso verificando o __class__atributo

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 classe:

>>> 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 é o __class__de qualquer um __class__?

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

Portanto, uma metaclasse é exatamente o que cria objetos de classe.

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

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

O __metaclass__atributo

No Python 2, você pode adicionar um __metaclass__atributo ao escrever uma classe (consulte a próxima seção para obter 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 class Foo(object)primeiro, mas o objeto de classe Fooainda não foi criado na memória.

O Python procurará __metaclass__na definição de classe. Se o encontrar, ele será usado para criar a classe de objeto Foo. Caso contrário, será usado typepara criar a classe.

Leia isso várias vezes.

Quando você faz:

class Foo(Bar):
    pass

Python faz o seguinte:

Existe um __metaclass__atributo em Foo?

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

Se o Python não conseguir encontrar __metaclass__, ele procurará um __metaclass__no nível MODULE e tentará fazer o mesmo (mas apenas para classes que não herdam nada, basicamente classes antigas).

Então, se não conseguir encontrar nenhuma __metaclass__, usará a Barmetaclasse do próprio (o primeiro pai) (que pode ser o padrão type) para criar o objeto de classe.

Tenha cuidado aqui, pois o __metaclass__atributo não será herdado, a metaclasse do pai ( Bar.__class__) será. Se Barusado um __metaclass__atributo criado Barcom type()(e não type.__new__()), as subclasses não herdarão esse comportamento.

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

A resposta é: algo que pode criar uma classe.

E o que pode criar uma classe? type, ou qualquer coisa que a subclasse ou use.

Metaclasses em Python 3

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

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

ou seja, o __metaclass__atributo 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 .

Uma coisa adicionada às metaclasses no python 3 é que você também pode passar atributos como argumentos de palavra-chave para uma metaclasse, assim:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Leia a seção abaixo para saber como o python lida com isso.

Metaclasses personalizadas

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

Você costuma fazer isso para APIs, onde deseja criar classes que correspondam ao contexto atual.

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

Dessa forma, todas as classes deste módulo serão criadas usando essa metaclasse, e só precisamos dizer à metaclasse para transformar todos os atributos em maiúsculas.

Felizmente, __metaclass__pode realmente ser exigível, não precisa ser uma classe formal (eu sei, algo com 'classe' no nome não precisa ser uma classe, vai entender ... mas é útil).

Então, começaremos 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_attrs):
    """
      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_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

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

__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'

Vamos checar:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'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_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Vamos reescrever o acima, mas com nomes de variáveis ​​mais curtos e mais realistas agora que sabemos o que eles significam:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Você pode ter notado o argumento extra cls. Não há nada de especial nisso: __new__sempre recebe a classe em que está definida, como primeiro parâmetro. Assim como você tem selfpara métodos comuns que recebem a instância como primeiro parâmetro ou a classe que define os métodos de classe.

Mas isso não é adequado OOP. Estamos ligando typediretamente e não estamos substituindo ou ligando para os pais __new__. Vamos fazer isso:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

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

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Ah, e no python 3, se você fizer essa chamada com argumentos de palavra-chave, assim:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

Isso se traduz na metaclasse para usá-lo:

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

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

A razão por trás da complexidade do código que usa metaclasses não é por causa de metaclasses, é porque você costuma usar metaclasses para fazer coisas distorcidas que dependem de introspecção, manipulação de 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 só, são simples:

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

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

Como você __metaclass__pode aceitar qualquer chamada, por que você usaria uma classe, uma vez que é obviamente mais complicada?

Existem vários motivos para fazer isso:

  • A intenção é clara. Quando você lê UpperAttrMetaclass(type), você sabe o que vai seguir
  • Você pode usar OOP. A metaclasse pode herdar da metaclasse, substituir os métodos pai. 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 é para 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 no __new__, __init__e __call__. O que permitirá que você faça coisas diferentes. Mesmo que geralmente você possa fazer tudo isso __new__, algumas pessoas ficam mais confortáveis ​​usando __init__.
  • Estes são chamados metaclasses, caramba! Deve significar alguma coisa!

Por que você usaria metaclasses?

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

Bem, geralmente você não:

Metaclasses são mágicas mais profundas com as quais 99% dos usuários nunca devem 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 porquê).

Guru do Python Tim Peters

O principal caso de uso de uma metaclasse é a criação de uma API. Um exemplo típico disso é o Django ORM. 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:

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

Não retornará um IntegerFieldobjeto. Ele retornará um inte pode levá-lo diretamente do banco de dados.

Isso é possível porque models.Modeldefine __metaclass__e usa alguma mágica que transformará o que Personvocê acabou de definir com instruções simples em um gancho complexo para 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 dessa 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, de fato, as classes 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 por type.

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

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

Em 99% das vezes você precisa de alteração de classe, é melhor usá-las.

Mas 98% das vezes, você não precisa de alteração de classe.

e-satis
fonte
30
Parece que no Django models.Modelele não usa, __metaclass__mas faz class Model(metaclass=ModelBase):referência a uma ModelBaseclasse que faz a mágica da metaclasse acima mencionada. Ótimo post! Aqui está a fonte de Django: github.com/django/django/blob/master/django/db/models/...
Max Goodridge
15
<< Cuidado aqui para que o __metaclass__atributo não seja herdado, a metaclasse do pai ( Bar.__class__) será. Se Barusado um __metaclass__atributo criado Barcom type()(e não type.__new__()), as subclasses não herdarão esse comportamento. >> - Você / alguém poderia explicar um pouco mais essa passagem?
precisa saber é
15
@MaxGoodridge Essa é a sintaxe do Python 3 para metaclasses. Veja Python 3.6 Modelo de dados VS Python 2.7 Modelo de dados
TBBle
2
Now you wonder why the heck is it written in lowercase, and not Type?- bem, porque ele é implementado em C - é a mesma razão defaultdict é minúsculas enquanto OrderedDict (em python 2) é CamelCase normais
Mr_and_Mrs_D
15
É uma resposta do wiki da comunidade (portanto, aqueles que comentaram com correções / melhorias podem considerar editar seus comentários na resposta, se tiver certeza de que estão corretos).
Brōtsyorfuzthrāx
403

Observe que esta resposta é para o Python 2.x, como foi escrito em 2008, as metaclasses são ligeiramente diferentes no 3.x.

Metaclasses são o molho secreto que faz a 'classe' funcionar. 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 de onde o nome, as bases e o ditado vêm neste exemplo de definição de classe.

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 tornará automaticamente as variáveis ​​na lista "atributos" configuradas na classe e definidas como Nenhuma.

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

Observe que o comportamento mágico que Initialisedobtém com a metaclasse init_attributesnão é passado para uma subclasse de Initialised.

Aqui está um exemplo ainda mais concreto, mostrando como você pode subclasse 'type' para criar 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
Jerub
fonte
169

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 teste que escrevi, eu queria acompanhar a ordem em que as classes foram definidas, para poder instancia-las posteriormente nessa ordem. 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

Tudo o que é uma subclasse MyTyperecebe um atributo de classe _orderque registra a ordem em que as classes foram definidas.

kindall
fonte
Obrigado pelo exemplo. Por que você achou isso mais fácil do que herdar do MyBase, de quem __init__(self)diz type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach
1
Eu queria que as próprias classes, não suas instâncias, fossem numeradas.
Kindall
Certo, duh. Obrigado. Meu código redefiniria o atributo MyType a cada instanciação e nunca definiria o atributo se uma instância do MyType nunca fosse criada. Opa (E uma propriedade de classe também pode trabalhar, mas ao contrário do metaclass oferece nenhum lugar óbvio para armazenar o balcão.)
Michael Gundlach
1
Este é um exemplo muito interessante, não menos importante, porque se pode realmente entender por que uma metaclasse pode ser necessária, para fornecer uma solução para uma dificuldade específica. OTOH Luto para me convencer de que alguém realmente precisaria instanciar objetos na ordem em que suas classes foram definidas: Acho que só precisamos aceitar sua palavra para isso :).
mike roedor
159

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 parecerá 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, no tempo de execução, os objetos Person são preenchidos com todos os tipos de métodos úteis. Veja a fonte para uma metaclasseria incrível.

Antti Rasinen
fonte
6
O uso de meta classes não adiciona novas propriedades e métodos a uma classe e não a uma instância? Tanto quanto eu entendi, a meta classe altera a própria classe e, como resultado, as instâncias podem ser construídas de maneira diferente pela classe alterada. Pode ser um pouco enganador para as pessoas que tentam entender a natureza de uma meta classe. Ter métodos úteis em instâncias pode ser alcançado por herança normal. A referência ao código do Django como exemplo é boa, no entanto.
trixn
119

Eu acho que a introdução do ONLamp à programação em metaclasse está bem escrita e fornece uma boa introdução ao tópico, apesar de já ter vários anos de idade.

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 )

Em resumo: Uma classe é um plano para a criação de uma instância, uma metaclasse é um plano para a criação de uma classe. Pode-se ver facilmente que, em Python, as classes também precisam ser objetos de primeira classe para permitir esse comportamento.

Eu nunca escrevi um, mas acho que um dos usos mais agradáveis ​​das metaclasses pode ser visto na estrutura do Django . As classes de modelo usam uma abordagem de metaclasse para permitir 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 delas é de 99%.

Matthias Kestenholz
fonte
109

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

TLDR: Uma metaclasse instancia e define o comportamento de uma classe, assim 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 Classvem? É uma instância de uma metaclasse (também pseudocódigo):

>>> Metaclass(...)
Class

Em 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 maneira 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, 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
  • Em outras palavras, uma metaclasse é uma classe de classe.

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

Quando você escreve uma definição de classe e o Python a executa, ele 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 as definições de classe para alterar o comportamento das 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 documentos :

Os usos potenciais para metaclasses são ilimitados. Algumas idéias que foram exploradas incluem criação de log, verificação de interface, delegação automática, criação automática de propriedades, 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 typecom os argumentos apropriados e atribuir o resultado a uma variável com esse nome:

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

Observe que algumas coisas são adicionadas automaticamente __dict__ao espaço para nome:

>>> 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 observação lateral sobre o conteúdo da classe __dict__: __module__existe porque as classes precisam saber onde estão definidas __dict__e __weakref__existem porque não definimos __slots__- se definirmos__slots__ , economizaremos um pouco de espaço nas instâncias, como podemos proibir __dict__e __weakref__excluí-los. Por exemplo:

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

... mas eu discordo.)

Podemos estender typecomo 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 é fornecê-lo __repr__. Quando ligamos help(repr), aprendemos que há um bom teste para um __repr__que também exige um teste de igualdade - obj == eval(repr(obj)). A implementação simples a seguir de __repr__e __eq__para instâncias de classe da nossa classe type fornece uma demonstração que pode melhorar o padrão __repr__das 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 verificar com (uma vez que as funções seriam bastante difíceis de avaliar a partir de seus padrões __repr__).

Um uso esperado: __prepare__um espaço para nome

Se, por exemplo, queremos saber em que ordem os métodos de uma classe são criados, poderíamos fornecer um ditado ordenado como o espaço para nome da classe. Nós faríamos isso com o __prepare__qual retorna o dict de namespace para a classe, se for implementado 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')

Observe que este exemplo foi adaptado da documentação - a nova enumeração 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 trataríamos com qualquer outra classe. Ele 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>})
Aaron Hall
fonte
78

Atualização do Python 3

Existem (neste momento) dois métodos principais em uma metaclasse:

  • __prepare__e
  • __new__

__prepare__permite fornecer um mapeamento personalizado (como um OrderedDict) para ser usado como espaço para nome enquanto a classe está sendo criada. Você deve retornar uma instância do espaço para nome que 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 simples, sem nada a ver, gostaria:

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 classe seria algo como:

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

Como você pode ver, você deve repetir o nome do atributo duas vezes. Isso possibilita erros de digitação, além de bugs irritantes.

Uma metaclasse simples pode solucionar esse problema:

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

É assim que a metaclasse seria (sem usar, __prepare__pois não é necessária):

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:

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, também poderia ter sido realizado com um decorador de classe, 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
Ethan Furman
fonte
Uau, esse é um novo recurso incrível que eu nem sabia que existia no Python 3. Obrigado pelo exemplo !!
Rich Lysakowski PhD
Observe que, desde o python 3.6, você pode usar __set_name__(cls, name)no descritor ( ValidateType) para definir o nome no descritor ( self.namee também neste caso self.attr). Isso foi adicionado para não ter que se aprofundar nas metaclasses para esse caso de uso comum específico (consulte PEP 487).
Lars
68

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

Se você tiver feito programação em Python por mais de alguns meses, acabará encontrando um código parecido com 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 é chamado 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 (por exemplo, quando criamos uma instância dela), na verdade, estamos chamando o __call__()método da metaclasse . Neste ponto, a maioria dos programadores de Python está um pouco confusa, porque eles disseram que, ao criar uma instância como essa, instance = SomeClass()você está chamando seu __init__()método. Alguns que cavou um pouco mais profundo saber que antes __init__()não __new__(). Bem, hoje outra camada de verdade está sendo revelada, antes que __new__()haja a metaclasse __call__().

Vamos estudar a cadeia de chamada 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 devolvê-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. Como typeé Meta_1a classe pai ( typesendo a metaclasse pai padrão) e considerando a sequência de pedidos da saída acima, agora temos uma pista sobre qual seria a pseudo implementação 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 é o primeiro chamado. Em seguida, delega a criação da instância para o __new__()método da classe e a inicialização para a instância __init__(). É também o que retorna a instância.

Do exposto acima, decorre que a metaclasse __call__()também tem a oportunidade de decidir se uma chamada Class_1.__new__()ou Class_1.__init__()será eventualmente feita. Ao longo de sua execução, ele poderia realmente retornar um objeto que não foi tocado por nenhum desses métodos. Tomemos, por exemplo, esta 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 ao tentar criar repetidamente 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
Michael Ekoka
fonte
Este é um bom complemento para a "resposta aceita" anteriormente votada. Ele fornece exemplos para os codificadores intermediários mastigarem.
Rich Lysakowski PhD
56

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 maneira diferente, mas optei por resolvê-lo usando uma metaclasse. Devido à complexidade, é um dos poucos módulos que escrevi em que os comentários no módulo superam 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()
Craig
fonte
43

A versão tl; dr

A type(obj)função fornece o tipo de um objeto.

O type()de uma classe é sua metaclasse .

Para usar uma metaclasse:

class Foo(object):
    __metaclass__ = MyMetaClass

typeé a sua própria metaclasse. A classe de uma classe é uma metaclasse - o corpo de uma classe são os argumentos passados ​​para a metaclasse que é usada para construir a classe.

Aqui você pode ler sobre como usar metaclasses para personalizar a construção de classes.

noɥʇʎԀʎzɐɹƆ
fonte
42

typeé realmente uma metaclass- uma classe que cria outras classes. A maioria metaclasssão as subclasses de type. O metaclassrecebe a newclasse como seu primeiro argumento e fornece acesso ao objeto de classe com os detalhes mencionados 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 em nenhum momento; o simples ato de criar a classe acionou a execução do metaclass.

Mushahid Khan
fonte
27

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

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

class foo:
    ...

As meta classes são usadas 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 deseje 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 é a lógica para dar suporte a chaves estrangeiras, o que envolve várias classes de registros.

Quando você define a metaclasse, 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. a metaclasse é poderosa, e acima não há uma lista próxima e exaustiva de usos para a metaclasse.

Xingzhou Liu
fonte
21

A função type () pode retornar o tipo de um objeto ou criar um novo tipo,

por exemplo, podemos criar uma classe Hi com a função type () e não precisamos usar dessa maneira com a classe Hi (object):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

Além de usar type () para criar classes dinamicamente, você pode controlar o comportamento de criação da classe e usar a metaclasse.

De acordo com o modelo de objeto Python, a classe é o objeto, portanto, a classe deve ser uma instância de outra determinada classe. Por padrão, uma classe Python é uma instância da classe type. Ou seja, type é metaclasse da maioria das classes internas e metaclasse das classes definidas pelo usuário.

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

O Magic entrará em vigor quando passamos argumentos de palavra-chave na metaclasse, indica que o intérprete Python cria a CustomList por meio do ListMetaclass. new (), neste ponto, podemos modificar a definição de classe, por exemplo, e adicionar um novo método e, em seguida, retornar a definição revisada.

binbjz
fonte
11

Além das respostas publicadas, posso dizer que a metaclassdefine o comportamento de uma classe. Portanto, você pode definir explicitamente sua metaclasse. Sempre que o Python obtém uma palavra class- chave , ele começa a procurar o arquivo 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__)

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'
Andy
fonte
4

Na programação orientada a objetos, uma metaclasse é uma classe cujas instâncias são classes. Assim como uma classe comum define o comportamento de certos objetos, uma metaclasse define o comportamento de determinada classe e suas instâncias. O termo metaclasse simplesmente significa algo usado para criar classes. Em outras palavras, é a classe de uma classe. A metaclasse é usada para criar a classe, assim como o objeto é uma instância de uma classe, uma classe é uma instância de uma metaclasse. Em python, as classes também são consideradas objetos.

Venu Gopal Tewari
fonte
Em vez de dar definições de livros, teria sido melhor se você tivesse adicionado alguns exemplos. A primeira linha da sua resposta parece ter sido copiada da entrada da Metaclasses na Wikipedia.
verisimilitude
@verisimilitude Também estou aprendendo, você pode me ajudar a melhorar essa resposta, fornecendo alguns exemplos práticos de sua experiência?
Venu Gopal Tewari
2

Aqui está outro exemplo do que pode ser usado:

  • Você pode usar o metaclasspara alterar a função de sua instância (a classe).
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

O metaclassé poderoso, há muitas coisas (como a magia dos macacos) que você pode fazer com isso, mas tome cuidado, pois isso só pode ser conhecido por você.

Carson
fonte
2

Uma classe, em Python, é um objeto e, como qualquer outro objeto, é uma instância de "alguma coisa". Esse "algo" é o que é chamado de metaclasse. Essa metaclasse é um tipo especial de classe que cria objetos de outras classes. Portanto, a metaclasse é responsável por criar novas classes. Isso permite que o programador personalize a maneira como as classes são geradas.

Para criar uma metaclasse, a substituição dos métodos new () e init () geralmente é feita. new () pode ser substituído para alterar a maneira como os objetos são criados, enquanto init () pode ser substituído para alterar a maneira de inicializar o objeto. A metaclasse pode ser criada de várias maneiras. Uma das maneiras é usar a função type (). A função type (), quando chamada com 3 parâmetros, cria uma metaclasse. Os parâmetros são: -

  1. Nome da classe
  2. Tupla com classes base herdadas por classe
  3. Um dicionário com todos os métodos e variáveis ​​de classe

Outra maneira de criar uma metaclasse compreende a palavra-chave 'metaclasse'. Defina a metaclasse como uma classe simples. Nos parâmetros da classe herdada, passe metaclass = metaclass_name

O metaclasse pode ser usado especificamente nas seguintes situações: -

  1. quando um efeito específico tiver que ser aplicado a todas as subclasses
  2. Mudança automática de classe (na criação) é necessária
  3. Por desenvolvedores de API
Swati Srivastava
fonte
2

Observe que no python 3.6 um novo método dunder __init_subclass__(cls, **kwargs)foi introduzido para substituir muitos casos de uso comuns de metaclasses. É chamado quando uma subclasse da classe de definição é criada. Veja documentos python .

Lars
fonte
-3

Metaclasse é um tipo de classe que define como a classe se comportará ou podemos dizer que Uma classe é ela própria uma instância de uma metaclasse.

AD técnico
fonte