¿Qué son las metaclases en Python?



Answers

Clases como objetos

Antes de entender las metaclases, necesita dominar las clases en Python. Y Python tiene una idea muy peculiar de qué clases son, tomadas del lenguaje Smalltalk.

En la mayoría de los idiomas, las clases son solo piezas de código que describen cómo producir un objeto. Eso también es cierto en Python:

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

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

Pero las clases son más que eso en Python. Las clases también son objetos.

Sí, objetos.

Tan pronto como utilice la class palabra clave, Python la ejecutará y creará un OBJETO. La instrucción

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

crea en memoria un objeto con el nombre "ObjectCreator".

Este objeto (la clase) es en sí mismo capaz de crear objetos (las instancias), y es por eso que es una clase .

Pero aún así, es un objeto, y por lo tanto:

  • puedes asignarlo a una variable
  • puedes copiarlo
  • puedes agregarle atributos
  • puedes pasarlo como un parámetro de función

p.ej:

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

Crear clases dinámicamente

Como las clases son objetos, puedes crearlos sobre la marcha, como cualquier objeto.

Primero, puedes crear una clase en una función 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>

Pero no es tan dinámico, ya que todavía tiene que escribir toda la clase usted mismo.

Como las clases son objetos, deben ser generadas por algo.

Cuando utiliza la palabra clave class , Python crea este objeto automáticamente. Pero como sucede con la mayoría de las cosas en Python, te da una forma de hacerlo manualmente.

Recuerde el type función? La buena función anterior que le permite saber qué tipo es un objeto:

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

Bueno, el type tiene una habilidad completamente diferente, también puede crear clases sobre la marcha. type puede tomar la descripción de una clase como parámetros y devolver una clase.

(Lo sé, es una tontería que la misma función pueda tener dos usos completamente diferentes según los parámetros que le pasen. Es un problema debido a la compatibilidad con versiones anteriores en Python)

type funciona de esta manera:

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

p.ej:

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

se puede crear manualmente de esta manera:

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

Notarás que usamos "MyShinyClass" como el nombre de la clase y como la variable para contener la referencia de clase. Pueden ser diferentes, pero no hay razón para complicar las cosas.

type acepta un diccionario para definir los atributos de la clase. Asi que:

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

Puede ser traducido a:

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

Y usado como una clase normal:

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

Y, por supuesto, puedes heredar de él, así que:

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

sería:

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

Eventualmente, querrás agregar métodos a tu clase. Simplemente defina una función con la firma adecuada y asígnela como un 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

Y puede agregar aún más métodos después de crear dinámicamente la clase, al igual que agregar métodos a un objeto de clase creado normalmente.

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

Ya ves a dónde vamos: en Python, las clases son objetos, y puedes crear una clase sobre la marcha, dinámicamente.

Esto es lo que hace Python cuando usa la class palabra clave, y lo hace usando una metaclase.

Qué son las metaclases (finalmente)

Las metaclases son las "cosas" que crean clases.

Usted define clases para crear objetos, ¿verdad?

Pero aprendimos que las clases de Python son objetos.

Bueno, las metaclases son las que crean estos objetos. Son las clases de las clases, puedes imaginarlas de esta manera:

MyClass = MetaClass()
my_object = MyClass()

Has visto que ese type te permite hacer algo como esto:

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

Es porque el type función es, de hecho, una metaclase. type es la metaclase que Python usa para crear todas las clases detrás de las escenas.

Ahora te preguntas por qué diablos está escrito en minúsculas, y no Type ?

Bueno, supongo que es una cuestión de coherencia con str , la clase que crea objetos de cadenas e int la clase que crea objetos enteros. type es solo la clase que crea objetos de clase.

Lo ves al verificar el atributo __class__ .

Todo, y me refiero a todo, es un objeto en Python. Eso incluye enteros, cadenas, funciones y clases. Todos ellos son objetos. Y todos ellos han sido creados a partir de una clase:

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

Ahora, ¿cuál es la __class__ de cualquier __class__ ?

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

Entonces, una metaclase es solo lo que crea objetos de clase.

Puede llamarlo una 'fábrica de clase' si lo desea.

type es la metaclase incorporada que Python usa, pero por supuesto, puede crear su propia metaclase.

El atributo __metaclass__

Puede agregar un atributo __metaclass__ cuando escribe una clase:

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

Si lo haces, Python usará la metaclase para crear la clase Foo .

Cuidado, es complicado.

Primero escribe class Foo(object) , pero el objeto de clase Foo no se ha creado aún en la memoria.

Python buscará __metaclass__ en la definición de la clase. Si lo encuentra, lo usará para crear la clase de objeto Foo . Si no lo hace, usará el type para crear la clase.

Lee eso varias veces.

Cuando tu lo hagas:

class Foo(Bar):
    pass

Python hace lo siguiente:

¿Hay un atributo __metaclass__ en Foo ?

Si es así, crea en memoria un objeto de clase (dije un objeto de clase, quédate conmigo aquí), con el nombre Foo usando lo que está en __metaclass__ .

Si Python no puede encontrar __metaclass__ , buscará una __metaclass__ a nivel de MÓDULO e intentará hacer lo mismo (pero solo para las clases que no heredan nada, básicamente clases de estilo antiguo).

Entonces, si no puede encontrar ninguna __metaclass__ en absoluto, usará la metaclase propia de la Bar (la primera matriz) (que podría ser el type predeterminado) para crear el objeto de la clase.

Tenga cuidado aquí que el atributo __metaclass__ no será heredado, la metaclase del padre ( Bar.__class__ ) será. Si Bar usó un atributo __metaclass__ que creó Bar con type() (y no type.__new__() ), las subclases no heredarán ese comportamiento.

Ahora la gran pregunta es, ¿qué puedes poner en __metaclass__ ?

La respuesta es: algo que puede crear una clase.

¿Y qué puede crear una clase? type , o cualquier cosa que subclases o lo use.

Metaclases personalizadas

El objetivo principal de una metaclase es cambiar la clase automáticamente, cuando se crea.

Por lo general, hace esto para las API, donde desea crear clases que coincidan con el contexto actual.

Imagina un ejemplo estúpido, donde decides que todas las clases de tu módulo deben tener sus atributos escritos en mayúsculas. Hay varias formas de hacerlo, pero una es establecer __metaclass__ en el nivel del módulo.

De esta forma, todas las clases de este módulo se crearán utilizando esta metaclase, y solo tenemos que decirle a la metaclase que convierta todos los atributos en mayúsculas.

Afortunadamente, __metaclass__ realmente puede ser invocable, no necesita ser una clase formal (lo sé, algo con 'clase' en su nombre no necesita ser una clase, imagínate ... pero es útil).

Entonces comenzaremos con un ejemplo simple, usando una función.

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

Ahora, hagamos exactamente lo mismo, pero usando una clase real para una metaclase:

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

Pero esto no es realmente POO. Llamamos al type directamente y no __new__ ni llamamos al padre __new__ . Vamos a hacerlo:

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)

Puede haber notado el argumento extra upperattr_metaclass . No tiene nada de especial: __new__ siempre recibe la clase en la que está definido, como primer parámetro. De la misma forma que lo hace con los métodos ordinarios que reciben la instancia como primer parámetro o la clase que define los métodos de clase.

Por supuesto, los nombres que usé aquí son largos en aras de la claridad, pero, como en el caso de self , todos los argumentos tienen nombres convencionales. Entonces, una metaclase de producción real se vería así:

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)

Podemos hacerlo aún más limpio mediante el uso de super , lo que facilitará la herencia (porque sí, puede tener metaclases, heredar de metaclases, heredar del 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)

Eso es. Realmente no hay nada más sobre metaclases.

La razón detrás de la complejidad del código que usa metaclases no se debe a las metaclases, sino porque usas metaclases para hacer cosas retorcidas que dependen de la introspección, la manipulación de la herencia, vars como __dict__ , etc.

De hecho, las metaclases son especialmente útiles para hacer magia negra, y por lo tanto cosas complicadas. Pero por sí mismos, son simples:

  • interceptar una creación de clase
  • modificar la clase
  • devolver la clase modificada

¿Por qué usarías clases de metaclases en lugar de funciones?

Como __metaclass__ puede aceptar cualquier llamador, ¿por qué __metaclass__ una clase ya que obviamente es más complicado?

Hay varias razones para hacerlo:

  • La intención es clara. Cuando lees UpperAttrMetaclass(type) , sabes lo que va a seguir
  • Puedes usar OOP. Metaclass puede heredar de metaclass, anular los métodos principales. Las metaclases incluso pueden usar metaclases.
  • Las subclases de una clase serán instancias de su metaclase si especificó una clase de metaclase, pero no con una función de metaclase.
  • Puedes estructurar mejor tu código. Nunca usas metaclases para algo tan trivial como el ejemplo anterior. Por lo general, es para algo complicado. Tener la capacidad de hacer varios métodos y agruparlos en una clase es muy útil para hacer que el código sea más fácil de leer.
  • Puedes conectar __new__ , __init__ y __call__ . Lo cual te permitirá hacer cosas diferentes. Incluso si generalmente puede hacerlo todo en __new__ , algunas personas se sienten más cómodas al usar __init__ .
  • Estos se llaman metaclases, ¡maldita sea! Debe significar algo!

¿Por qué usarías metaclases?

Ahora la gran pregunta. ¿Por qué usarías alguna función oscura propensa a errores?

Bueno, por lo general, no:

Las metaclases son una magia más profunda de la que el 99% de los usuarios nunca deberían preocuparse. Si se pregunta si los necesita, no (las personas que realmente los necesitan saben con certeza que los necesitan y no necesitan una explicación sobre por qué).

Python Guru Tim Peters

El caso de uso principal para una metaclase es crear una API. Un ejemplo típico de esto es el Django ORM.

Te permite definir algo como esto:

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

Pero si haces esto:

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

No devolverá un objeto IntegerField . int un int , e incluso puede tomarlo directamente de la base de datos.

Esto es posible porque models.Model define __metaclass__ y usa algo de magia que convertirá a la Person que acaba de definir con declaraciones simples en un enganche complejo a un campo de base de datos.

Django hace que algo complejo parezca simple exponiendo una API simple y usando metaclases, recreando el código de esta API para hacer el trabajo real detrás de las escenas.

La última palabra

En primer lugar, usted sabe que las clases son objetos que pueden crear instancias.

Bueno, de hecho, las clases son en sí mismas instancias. De metaclases

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

Todo es un objeto en Python, y todos son instancias de clases o instancias de metaclases.

Excepto por type .

type es en realidad su propia metaclase. Esto no es algo que puedas reproducir en Python puro, y se hace engañando un poco en el nivel de implementación.

En segundo lugar, las metaclases son complicadas. Puede que no quieras usarlos para alteraciones de clase muy simples. Puedes cambiar las clases usando dos técnicas diferentes:

El 99% del tiempo necesitas una alteración de clase, es mejor que los uses.

Pero el 98% del tiempo, no necesitas alteración de clase en absoluto.

Question

¿Qué son las metaclases y para qué las utilizamos?




Un uso para las metaclases es agregar nuevas propiedades y métodos a una instancia de forma automática.

Por ejemplo, si observas los modelos de Django , su definición parece un poco confusa. Parece que solo está definiendo propiedades de clase:

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

Sin embargo, en tiempo de ejecución, los objetos Persona se llenan con todo tipo de métodos útiles. Vea la source de algunos sorprendentes metaclassery.




Role of a metaclass's __call__() method when creating a class instance

If you've done Python programming for more than a few months you'll eventually stumble upon code that looks like this:

# 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')

The latter is possible when you implement the __call__() magic method on the class.

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

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

The __call__() method is invoked when an instance of a class is used as a callable. But as we've seen from previous answers a class itself is an instance of a metaclass, so when we use the class as a callable (ie when we create an instance of it) we're actually calling its metaclass's __call__() method. At this point most Python programmers are a bit confused because they've been told that when creating an instance like this instance = SomeClass() you're calling it's __init__() method. Some who've dug a bit deeper know that before __init__() there's __new__() . Well, today another layer of truth is being revealed, before __new__() there's the metaclass's __call__() .

Let's study the method call chain from specifically the perspective of creating an instance of a class.

This is a metaclass that logs exactly the moment before an instance is created and the moment it's about to return it.

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

This is a class that uses that metaclass

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__()."

And now let's create an instance of 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.

The code above doesn't actually do anything other than logging the task and then delegating the actual work to the parent (ie keeping the default behavior). So with type being Meta_1 's parent class, we can imagine that this would be the pseudo implementation of 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

We can see that the metaclass's __call__() method is the one that's called first. It then delegates creation of the instance to the class's __new__() method and initialization to the instance's __init__() . It's also the one that ultimately returns the instance.

From the above it stems that the metaclass's __call__() is also given the opportunity to decide whether or not a call to Class_1.__new__() or Class_1.__init__() will eventually be made. Over the course of its execution it could actually return an object that hasn't been touched by either of these methods. Take for example this approach to the singleton pattern:

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__()."

Let's observe what happens when repeatedly trying to create an object of type 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



The type() function can return the type of an object or create a new type,

for example, we can create a Hi class with the type() function and do not need to use this way with class 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

In addition to using type() to create classes dynamically, you can control creation behavior of class and use metaclass.

According to the Python object model, the class is the object, so the class must be an instance of another certain class. By default, a Python class is instance of the type class. That is, type is metaclass of most of the built-in classes and metaclass of user-defined classes.

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

Magic will take effect when we passed keyword arguments in metaclass, it indicates the Python interpreter to create the CustomList through ListMetaclass. new (), at this point, we can modify the class definition, for example, and add a new method and then return the revised definition.




Creo que la introducción de ONLamp a la programación de metaclases está bien escrita y ofrece una muy buena introducción al tema a pesar de tener ya varios años.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

En resumen: una clase es un modelo para la creación de una instancia, una metaclase es un modelo para la creación de una clase. Se puede ver fácilmente que en Python las clases también deben ser objetos de primera clase para permitir este comportamiento.

Nunca he escrito uno, pero creo que uno de los mejores usos de las metaclases se puede ver en el marco de Django . Las clases modelo usan un enfoque de metaclase para permitir un estilo declarativo de escribir nuevos modelos o formar clases. Mientras que la metaclase está creando la clase, todos los miembros tienen la posibilidad de personalizar la clase en sí.

Lo que queda por decir es: si no sabes qué son las metaclases, la probabilidad de que no las necesites es del 99%.




Python 3 update

There are (at this point) two key methods in a metaclass:

  • __prepare__ , and
  • __new__

__prepare__ lets you supply a custom mapping (such as an OrderedDict ) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don't implement __prepare__ a normal dict is used.

__new__ is responsible for the actual creation/modification of the final class.

A bare-bones, do-nothing-extra metaclass would like:

class Meta(type):

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

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

Un simple ejemplo:

Say you want some simple validation code to run on your attributes -- like it must always be an int or a str . Without a metaclass, your class would look something like:

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

As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.

A simple metaclass can address that problem:

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

This is what the metaclass would look like (not using __prepare__ since it is not needed):

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)

A sample run of:

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

produce:

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

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.

The 'ValidateType' class for reference:

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



The tl;dr version

The type(obj) function gets you the type of an object.

The type() of a class is its metaclass .

To use a metaclass:

class Foo(object):
    __metaclass__ = MyMetaClass



Links