¿Qué son las metaclases en Python?

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

Una metaclase es la clase de una clase. Una clase define cómo se comporta una instancia de la clase (es decir, un objeto), mientras que una metaclase define cómo se comporta una clase. Una clase es una instancia de una metaclase.

Mientras que en Python puede usar callables arbitrarios para las metaclases (como lo muestra Jerub ), el mejor enfoque es convertirlo en una clase en sí. type es la metaclase habitual en Python. type es en sí mismo una clase, y es su propio tipo. No podrás recrear algo como el type puramente en Python, pero Python hace un poco de trampa. Para crear tu propia metaclase en Python, solo quieres subclasificar.

Una metaclase se usa más comúnmente como una clase de fábrica. Cuando creas un objeto llamando a la clase, Python crea una nueva clase (cuando ejecuta la statement de ‘clase’) llamando a la metaclase. Combinadas con los __init__ normales __init__ y __new__ , las metaclases le permiten hacer “cosas adicionales” al crear una clase, como registrar la nueva clase con algún registro o reemplazar la clase con algo completamente distinto.

Cuando se ejecuta la instrucción de class , Python ejecuta primero el cuerpo de la instrucción de class como un bloque de código normal. El espacio de nombres resultante (un dict) contiene los atributos de la clase a ser. La metaclase se determina observando las clases de base de la clase a ser (las metaclases se heredan), el atributo __metaclass__ de la clase a ser (si existe) o la variable global __metaclass__ . Luego se llama a la metaclase con el nombre, las bases y los atributos de la clase para instanciarla.

Sin embargo, las metaclases realmente definen el tipo de una clase, no solo una fábrica para ella, así que puedes hacer mucho más con ellas. Por ejemplo, puede definir métodos normales en la metaclase. Estos métodos de metaclase son como métodos de clase, ya que pueden llamarse en la clase sin una instancia, pero tampoco son métodos de clase porque no pueden llamarse en una instancia de la clase. type.__subclasses__() es un ejemplo de un método en el type metaclass. También puede definir los métodos ‘mágicos’ normales, como __add__ , __iter__ y __getattr__ , para implementar o cambiar el comportamiento de la clase.

Aquí hay un ejemplo agregado de los bits y piezas:

 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__ 

Clases como objetos

Antes de comprender las metaclases, necesitas 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 usa la class palabra clave, Python la ejecuta y crea un OBJETO. La instrucción

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

crea en memoria un objeto con el nombre “ObjectCreator”.

Este objeto (la clase) es 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 parámetro de función.

p.ej:

 >>> print(ObjectCreator) # you can print a class because it's an object  >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter  >>> 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> 

Creando clases dinámicamente

Dado que las clases son objetos, puede crearlos sobre la marcha, como cualquier objeto.

Primero, puedes crear una clase en una función usando la 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  >>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c> 

Pero no es tan dynamic, ya que todavía tienes que escribir toda la clase.

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

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

¿Recuerdas el type función? La buena función antigua que te permite saber qué tipo de objeto es:

 >>> print(type(1))  >>> print(type("1"))  >>> print(type(ObjectCreator))  >>> print(type(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 pases. 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)  >>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec> 

Notará que usamos “MyShinyClass” como el nombre de la clase y como la variable para mantener la referencia de la 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 

Se puede traducir a:

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

Y usado como una clase normal:

 >>> print(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)  >>> print(FooChild.bar) # bar is inherited from Foo True 

Eventualmente querrá agregar métodos a su clase. Simplemente defina una función con la firma adecuada y asígnele 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 incluso 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 

Usted ve a dónde vamos: en Python, las clases son objetos, y puede 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.

Definís 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 verlas de esta manera:

 MyClass = MetaClass() my_object = MyClass() 

Has visto que el 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 escena.

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

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

Usted ve eso al verificar el atributo __class__ .

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

 >>> age = 35 >>> age.__class__  >>> name = 'bob' >>> name.__class__  >>> def foo(): pass >>> foo.__class__  >>> class Bar(object): pass >>> b = Bar() >>> b.__class__  

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

 >>> age.__class__.__class__  >>> name.__class__.__class__  >>> foo.__class__.__class__  >>> b.__class__.__class__  

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

Puedes llamarlo ‘fábrica de clase’ si lo deseas.

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

El atributo __metaclass__

En Python 2, puede agregar un atributo __metaclass__ cuando escribe una clase (vea la siguiente sección para la syntax de Python 3):

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

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

Cuidado, es complicado.

Primero escribe la class Foo(object) , pero el objeto de clase Foo no se ha creado 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á type para crear la clase.

Lo leí varias veces.

Cuando tu lo hagas:

 class Foo(Bar): pass 

Python hace lo siguiente:

¿Hay un atributo __metaclass__ en Foo ?

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

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

Luego, si no puede encontrar ninguna __metaclass__ , utilizará la propia metaclase de la Bar (el primer padre) (que podría ser el type predeterminado) para crear el objeto de la clase.

Tenga cuidado aquí de que el atributo __metaclass__ no se heredará, la metaclase del padre ( Bar.__class__ ) será. Si Bar utilizó un atributo __metaclass__ que creó la Bar con type() (y no el 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 las subclases o lo use.

Metaclases en Python 3

La syntax para establecer la metaclase se ha cambiado en Python 3:

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

es decir, el atributo __metaclass__ ya no se usa, a favor de un argumento de palabra clave en la lista de clases base.

Sin embargo, el comportamiento de las metaclases sigue siendo en gran medida el mismo .

Una cosa que se agregó a las metaclases en Python 3 es que también puedes pasar atributos como argumentos de palabras clave a una metaclase, de esta manera:

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

Lee la siguiente sección para saber cómo Python maneja esto.

Metaclases personalizados

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

Normalmente haces esto para las API, donde quieres crear clases que coincidan con el contexto actual.

Imagine un ejemplo estúpido, donde decide que todas las clases en su módulo deben tener sus atributos escritos en mayúsculas. Hay varias formas de hacer esto, pero una forma es establecer __metaclass__ en el nivel del módulo.

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

Afortunadamente, __metaclass__ puede ser realmente invocable, no es necesario que sea una clase formal (lo sé, algo con ‘clase’ en su nombre no tiene que 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 OOP. Llamamos a type directamente y no __new__ ni llamamos a los padres __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) 

Es posible que hayas notado el argumento adicional upperattr_metaclass . No tiene nada de especial: __new__ siempre recibe la clase en la que está definido, como primer parámetro. Al igual que usted tiene self para los métodos comunes que reciben la instancia como primer parámetro, o la clase definitoria para los métodos de clase.

Por supuesto, los nombres que utilicé aquí son largos por razones de claridad, pero al igual que para mí, todos los argumentos tienen nombres convencionales. Así que 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 utilizando super , lo que facilitará la herencia (porque sí, puede tener metaclases, heredar de metaclases, heredar de 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) 

Ah, y en Python 3 si haces esta llamada con argumentos de palabras clave, como este:

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

Se traduce a esto en la metaclase para usarlo:

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

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

La razón detrás de la complejidad del código que usan las metaclases no se debe a las metaclases, sino porque usualmente se usan 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?

Ya que __metaclass__ puede aceptar cualquier llamada, ¿por qué usaría 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. Metaclase puede heredar de metaclase, anular métodos primarios. Las metaclases pueden incluso utilizar metaclases.
  • Las subclases de una clase serán instancias de su metaclase si ha especificado una clase de metaclase, pero no con una función de metaclase.
  • Puedes estructurar mejor tu código. Nunca use 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 enganchar a __new__ , __init__ y __call__ . Lo que te permitirá hacer cosas diferentes. Incluso si normalmente puedes hacerlo todo en __new__ , algunas personas se sienten más cómodas usando __init__ .
  • Estas se llaman metaclases, maldita sea! ¡Debe significar algo!

¿Por qué usarías metaclases?

Ahora la gran pregunta. ¿Por qué usaría alguna característica oscura propensa a errores?

Bueno, normalmente no lo haces

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

Python Gurú Tim Peters

El caso de uso principal de 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 enlace complejo a un campo de base de datos.

Django hace que algo complejo parezca simple al exponer una API simple y al usar metaclases, recreando el código de esta API para hacer el trabajo real entre bambalinas.

La última palabra

Primero, sabes 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 el type .

type es en realidad su propia metaclase. Esto no es algo que se pueda reproducir en Python puro, y se hace haciendo trampa un poco en el nivel de implementación.

En segundo lugar, las metaclases son complicadas. Es posible que no desee utilizarlos para modificaciones de clase muy simples. Puedes cambiar de clase usando dos técnicas diferentes:

  • parches de mono
  • decoradores de clase

El 99% de las veces que necesita una alteración de clase, es mejor usarlas.

Pero el 98% de las veces, no necesitas ninguna alteración de clase.

Tenga en cuenta que esta respuesta es para Python 2.x como se escribió en 2008, las metaclases son ligeramente diferentes en 3.x, vea los comentarios.

Las metaclases son la salsa secreta que hace que la clase funcione. La metaclase predeterminada para un nuevo objeto de estilo se llama “tipo”.

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

Las metaclases toman 3 argumentos. ‘ nombre ‘, ‘ bases ‘ y ‘ dict

Aquí es donde comienza el secreto. Busque de dónde vienen el nombre, las bases y el dictado en esta definición de clase de ejemplo.

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

Permite definir una metaclase que demostrará cómo ‘ class: ‘ lo llama.

 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 (, None, , 1) The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__'] TestName = 'yellow' 

Y ahora, un ejemplo que realmente significa algo, esto hará que las variables en la lista de “atributos” se establezcan en la clase, y se establezca en Ninguno.

 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 

Tenga en cuenta que el comportamiento mágico que se “Initaliza” gana al tener los init_attributes metaclase no se pasa a una subclase de Initalizado.

Aquí hay un ejemplo aún más concreto, que muestra cómo puede subclasificar ‘tipo’ para hacer una metaclase que realice una acción cuando se crea la clase. Esto es 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 

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

Por ejemplo, si nos fijamos en los modelos Django , su definición parece un poco confusa. Parece como si solo estuvieras 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 el tiempo de ejecución, los objetos Person están llenos de todo tipo de métodos útiles. Vea la fuente de algunos metaclassery asombrosos.

Otros han explicado cómo funcionan las metaclases y cómo encajan en el sistema de tipo Python. Aquí hay un ejemplo de para qué se pueden usar. En un marco de prueba que escribí, quería hacer un seguimiento del orden en el que se definían las clases, para poder luego instanciarlas en este orden. Me resultó más fácil hacer esto usando una metaclase.

 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 

Cualquier cosa que sea una subclase de MyType obtiene un atributo de clase _order que registra el orden en que se definieron las clases.

Creo que la introducción SOLO a la progtwigción de metaclase está bien escrita y ofrece una muy buena introducción al tema a pesar de que ya tiene varios años.

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

En resumen: una clase es un plano para la creación de una instancia, una metaclase es un plano 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 habilitar este comportamiento.

Nunca he escrito una, pero creo que uno de los mejores usos de las metaclases se puede ver en el marco de Django . Las clases modelo utilizan un enfoque de metaclase para habilitar un estilo declarativo de escritura de nuevos modelos o clases de formulario. Mientras la metaclase crea la clase, todos los miembros tienen la posibilidad de personalizar la clase en sí.

  • Creando un nuevo modelo
  • La metaclase que permite esto.

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

¿Qué son las metaclases? ¿Para qué los utiliza?

TLDR: Una metaclase crea instancias y define el comportamiento de una clase, al igual que una clase crea instancias y define el comportamiento de una instancia.

Pseudocódigo

 >>> Class(...) instance 

Lo anterior debe parecer familiar. Bueno, ¿de dónde viene la Class ? Es una instancia de una metaclase (también pseudocódigo):

 >>> Metaclass(...) Class 

En código real, podemos pasar la metaclase predeterminada, el type , todo lo que necesitamos para crear una instancia de una clase y obtenemos una clase:

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

Poniéndolo diferente

  • Una clase es para una instancia como una metaclase es para una clase.

    Cuando instanciamos un objeto, obtenemos una instancia:

     >>> object() # instantiation of class  # instance 

    Del mismo modo, cuando definimos una clase explícitamente con la metaclase predeterminada, type , creamos una instancia de ella:

     >>> type('Object', (object,), {}) # instantiation of metaclass  # instance 
  • Dicho de otra manera, una clase es una instancia de una metaclase:

     >>> isinstance(object, type) True 
  • Dicho de otra manera, una metaclase es una clase de clase.

     >>> type(object) == type True >>> object.__class__  

Cuando escribe una definición de clase y Python la ejecuta, utiliza una metaclase para crear una instancia del objeto de la clase (que, a su vez, se usará para crear instancias de esa clase).

Al igual que podemos usar definiciones de clase para cambiar el comportamiento de las instancias de objetos personalizados, podemos usar una definición de clase de metaclase para cambiar el comportamiento de un objeto de clase.

¿Para qué se pueden usar? De los documentos :

Los usos potenciales de las metaclases son ilimitados. Algunas ideas que se han explorado incluyen el registro, la comprobación de la interfaz, la delegación automática, la creación automática de propiedades, los proxies, los marcos y el locking / sincronización automática de recursos.

Sin embargo, generalmente se recomienda a los usuarios evitar el uso de metaclases a menos que sea absolutamente necesario.

Usas una metaclase cada vez que creas una clase:

Cuando escribes una definición de clase, por ejemplo, como esta,

 class Foo(object): 'demo' 

Se crea una instancia de un objeto de clase.

 >>> Foo  >>> isinstance(Foo, type), isinstance(Foo, object) (True, True) 

Es lo mismo que llamar funcionalmente al type con los argumentos apropiados y asignar el resultado a una variable de ese nombre:

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

Tenga en cuenta que algunas cosas se agregan automáticamente a __dict__ , es decir, el espacio de nombres:

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

La metaclase del objeto que creamos, en ambos casos, es de type .

(Una nota al margen sobre el contenido de la clase __dict__ : __module__ está ahí porque las clases deben saber dónde están definidas, y __dict__ y __weakref__ están ahí porque no definimos __slots__ – si definimos __slots__ un poco de espacio en las instancias, ya que podemos rechazar __dict__ y __weakref__ excluyéndolos. Por ejemplo:

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

… pero yo divago.)

Podemos extender el type como cualquier otra definición de clase:

Aquí está el __repr__ predeterminado de las clases:

 >>> Foo  

Una de las cosas más valiosas que podemos hacer de manera predeterminada al escribir un objeto de Python es proporcionarle una buena __repr__ . When we call help(repr) we learn that there’s a good test for a __repr__ that also requires a test for equality – obj == eval(repr(obj)) . The following simple implementation of __repr__ and __eq__ for class instances of our type class provides us with a demonstration that may improve on the default __repr__ of 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__) 

So now when we create an object with this metaclass, the __repr__ echoed on the command line provides a much less ugly sight than the default:

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

With a nice __repr__ defined for the class instance, we have a stronger ability to debug our code. However, much further checking with eval(repr(Class)) is unlikely (as functions would be rather impossible to eval from their default __repr__ ‘s).

An expected usage: __prepare__ a namespace

If, for example, we want to know in what order a class’s methods are created in, we could provide an ordered dict as the namespace of the class. We would do this with __prepare__ which returns the namespace dict for the class if it is implemented in 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 

Y uso:

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

And now we have a record of the order in which these methods (and other class attributes) were created:

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

Note, this example was adapted from the documentation – the new enum in the standard library does this.

So what we did was instantiate a metaclass by creating a class. We can also treat the metaclass as we would any other class. It has a method resolution order:

 >>> inspect.getmro(OrderedType) (, , , ) 

And it has approximately the correct repr (which we can no longer eval unless we can find a way to represent our functions.):

 >>> OrderedMethodsObject OrderedType('OrderedMethodsObject', (object,), {'method1': , 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': , 'method2': , '__module__': '__main__', '__weakref__': , '__doc__': None, '__d ict__': , 'method4': }) 

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 ejemplo simple:

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' 

produces:

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

Note : This example is simple enough it could have also been accomplished with a class decorator, but presumbly 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 

Role of a metaclass’ __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’ __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 its __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’ __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_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 that the code above doesn’t actually do anything more than logging the tasks. Each method delegates the actual work to its parent’s implementation, thus keeping the default behavior. Since type is Meta_1 ‘s parent class ( type being the default parent metaclass) and considering the ordering sequence of the output above, we now have a clue as to what 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’ __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’ __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() #  singleton returning from Meta_2.__call__(), skipping creation of new instance. c = Class_2() #  singleton returning from Meta_2.__call__(), skipping creation of new instance. a is b is c # True 

A metaclass is a class that tells how (some) other class should be created.

This is a case where I saw metaclass as a solution to my problem: I had a really complicated problem, that probably could have been solved differently, but I chose to solve it using a metaclass. Because of the complexity, it is one of the few modules I have written where the comments in the module surpass the amount of code that has been written. Here it is…

 #!/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 # paird 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('', 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() 

type is actually a metaclass — a class that creates another classes. Most metaclass are the subclasses of type . The metaclass receives the new class as its first argument and provide access to class object with details as mentioned below:

 >>> 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 : Defining class  get_choch :'dairy' __module__ :'builtins' __qualname__ :'NewClass' 

Note:

Notice that the class was not instantiated at any time; the simple act of creating the class triggered execution of the metaclass .

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 

Python classes are themselves objects – as in instance – of their meta-class.

The default metaclass, which is applied when when you determine classes as:

 class foo: ... 

meta class are used to apply some rule to an entire set of classes. For example, suppose you’re building an ORM to access a database, and you want records from each table to be of a class mapped to that table (based on fields, business rules, etc..,), a possible use of metaclass is for instance, connection pool logic, which is share by all classes of record from all tables. Another use is logic to to support foreign keys, which involves multiple classes of records.

when you define metaclass, you subclass type, and can overrided the following magic methods to insert your logic.

 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": , "foo": } 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 

anyhow, those two are the most commonly used hooks. metaclassing is powerful, and above is nowhere near and exhaustive list of uses for metaclassing.

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.

In addition to the published answers I can say that a metaclass defines the behaviour for a class. So, you can explicitly set your metaclass. Whenever Python gets a keyword class then it starts searching for the metaclass . If it’s not found – the default metaclass type is used to create the class’s object. Using the __metaclass__ attribute, you can set metaclass of your class:

 class MyClass: __metaclass__ = type # write here other method # write here one more method print(MyClass.__metaclass__) 

It’ll produce the output like this:

 class 'type' 

And, of course, you can create your own metaclass to define the behaviour of any class that are created using your class.

For doing that, your default metaclass type class must be inherited as this is the main 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__) 

La salida será:

 class '__main__.MyMetaClass' class 'type'