¿Qué son las metaclases en Python?

6324

En Python, ¿qué son las metaclases y para qué las usamos?

0
3295

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 metaclases (como muestra Jerub ), el mejor enfoque es convertirlo en una clase real en sí misma. typees la metaclase habitual en Python. typees en sí misma una clase y es de su propio tipo. No podrá recrear algo como typepuramente en Python, pero Python hace un poco de trampa. Para crear su propia metaclase en Python, realmente solo desea crear una subclase type.

Una metaclase se usa más comúnmente como una fábrica de clases. Cuando crea un objeto llamando a la clase, Python crea una nueva clase (cuando ejecuta la declaración 'clase') llamando a la metaclase. Combinadas con los métodos normales __init__y __new__, las metaclases por lo tanto 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 diferente.

Cuando classse ejecuta la declaración, Python primero ejecuta el cuerpo de la classdeclaración como un bloque de código normal. El espacio de nombres resultante (un dict) contiene los atributos de la clase futura. La metaclase se determina observando las clases base de la clase futura (las metaclases se heredan), el __metaclass__atributo de la clase futura (si existe) o la __metaclass__variable global. A continuación, 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 clase, no solo una fábrica para ella, por lo que puede hacer mucho más con ellas. Puede, por ejemplo, definir métodos normales en la metaclase. Estos métodos de metaclase son como métodos de clase en el sentido de que se pueden llamar en la clase sin una instancia, pero tampoco son como métodos de clase en el sentido de que no se pueden llamar en una instancia de la clase. type.__subclasses__()es un ejemplo de un método en la typemetaclase. También puede definir los métodos 'mágicos' normales, como __add__, __iter__y __getattr__, para implementar o cambiar cómo se comporta 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__
10
  • 17
    class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
    pppery
    3 de agosto de 2017 a las 14:34
  • 30
    ppperry obviamente quiso decir que no se puede recrear el tipo sin usar el tipo en sí mismo como metaclase. Lo cual es bastante justo decirlo. 18 de septiembre de 2018 a las 23:24
  • 4
    ¿No debería llamarse unregister () por instancia de la clase Example? 29/11/18 a las 0:59
  • 17
    Tenga en cuenta que __metaclass__no es compatible con Python 3. En el uso de Python 3 class MyObject(metaclass=MyType), consulte python.org/dev/peps/pep-3115 y la respuesta a continuación. 1 de mayo de 2019 a las 8:36
  • 3
    La documentación describe cómo se elige la metaclase . La metaclase no se hereda tanto como se deriva. Si especifica una metaclase, debe ser un subtipo de cada metaclase de clase base; de lo contrario, utilizará la metaclase de una clase base que es un subtipo de cada una de las otras metaclase de clase base. Tenga en cuenta que es posible que no se pueda encontrar una metaclase válida y la definición fallará.
    chepner
    9 de ene. De 2020 a las 21:14
7392
+250

Clases como objetos

Antes de comprender las metaclases, debe dominar las clases en Python. Y Python tiene una idea muy peculiar de qué son las clases, tomada del lenguaje Smalltalk.

En la mayoría de los lenguajes, las clases son solo fragmentos 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 palabra clave class, Python la ejecuta y crea un objeto . La instrucción

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

crea en la memoria un objeto con el nombre ObjectCreator.

Este objeto (la clase) es en sí mismo capaz de crear objetos (las instancias), y por eso 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>

Creando clases dinámicamente

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

Primero, puede 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 tienes que escribir toda la clase tú mismo.

Dado que las clases son objetos, deben ser generadas por algo.

Cuando usa la classpalabra clave, Python crea este objeto automáticamente. Pero como con la mayoría de las cosas en Python, le brinda una forma de hacerlo manualmente.

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

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

Bueno, typetiene una habilidad completamente diferente, también puede crear clases sobre la marcha. typepuede 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 de acuerdo con los parámetros que le pasas. Es un problema debido a la compatibilidad con versiones anteriores en Python)

type funciona de esta manera:

type(name, bases, attrs)

Dónde:

  • name: nombre de la clase
  • bases: tupla de la clase padre (para herencia, puede estar vacía)
  • attrs: diccionario que contiene los nombres y valores de los atributos

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 MyShinyClasscomo el nombre de la clase y como la variable para contener la referencia de la clase. Pueden ser diferentes, pero no hay por qué complicar las cosas.

typeacepta un diccionario para definir los atributos de la clase. Entonces:

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

Puede traducirse a:

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

Y usado como 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 heredarlo, 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á agregar métodos a su clase. Simplemente defina una función con la firma adecuada y asígnela como 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

Verá hacia dónde vamos: en Python, las clases son objetos y puede crear una clase sobre la marcha, de forma dinámica.

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

Que son las metaclases (finalmente)

Las metaclases son las 'cosas' que crean clases.

Tú defines 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 typete permite hacer algo como esto:

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

Es porque la función typees de hecho una metaclase. typees la metaclase que Python usa para crear todas las clases detrás de escena.

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

Bueno, supongo que es una cuestión de coherencia con strla clase que crea objetos de cadenas y intla clase que crea objetos enteros. typees solo la clase que crea objetos de clase.

Lo ve al verificar el __class__atributo.

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 bien, ¿cuál es el __class__de cualquiera __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 clases' si lo desea.

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

El __metaclass__atributo

En Python 2, puede agregar un __metaclass__atributo cuando escribe una clase (consulte la siguiente sección para conocer la sintaxis de Python 3):

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

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

Cuidado, es complicado.

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

Python buscará __metaclass__en la definición de clase. Si lo encuentra, lo usará para crear la clase de objeto Foo. Si no es así, se utilizará typepara crear la clase.

Léelo varias veces.

Cuando tu lo hagas:

class Foo(Bar):
    pass

Python hace lo siguiente:

¿Hay algún __metaclass__atributo 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 Foousando lo que está en __metaclass__.

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

Luego, si no puede encontrar ninguno __metaclass__, usará la Barpropia metaclase (el primer padre) (que podría ser la predeterminada type) para crear el objeto de clase.

Tenga cuidado aquí de que el __metaclass__atributo no se heredará, la metaclase del padre ( Bar.__class__) lo será. Si se Barusa un __metaclass__atributo que se creó Barcon type()(y no type.__new__()), las subclases no heredarán ese comportamiento.

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

La respuesta es algo que puede crear una clase.

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

Metaclases en Python 3

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

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

es decir, el __metaclass__atributo 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 permanece prácticamente igual .

Una cosa que se agrega a las metaclases en Python 3 es que también puede pasar atributos como argumentos de palabras clave a una metaclase, así:

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

Lea la sección a continuación para saber cómo Python maneja esto.

Metaclases personalizadas

El propósito 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, en el que decides que todas las clases de tu módulo deben tener sus atributos escritos en mayúsculas. Hay varias formas de hacer esto, pero una es configurarla __metaclass__a nivel de 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 cambie todos los atributos a mayúsculas.

Afortunadamente, en __metaclass__realidad puede ser cualquier 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_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 a revisar:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'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_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)

Reescribamos lo anterior, pero con nombres de variable más cortos y realistas ahora que sabemos lo que significan:

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)

Es posible que haya notado el argumento adicional cls. No tiene nada de especial: __new__siempre recibe la clase en la que está definida, como primer parámetro. Al igual que tiene selfpara los métodos ordinarios que reciben la instancia como el primer parámetro, o la clase de definición para los métodos de clase.

Pero esto no es una programación orientada a objetos adecuada. Estamos llamando typedirectamente y no anulamos ni llamamos a los padres __new__. Hagámoslo en su lugar:

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 hacerlo aún más limpio usando 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, 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, y en Python 3 si haces esta llamada con argumentos de palabras clave, así:

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

Se traduce a esto en la metaclase para usarlo:

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

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

La razón detrás de la complejidad del código que usa metaclases no se debe a las metaclases, es porque usualmente usa metaclases para hacer cosas retorcidas confiando en la introspección, manipulando la herencia, variables 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 la creación de una clase
  • modificar la clase
  • devolver la clase modificada

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

Dado que __metaclass__puede aceptar cualquier invocable, ¿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 seguirá
  • Puede utilizar OOP. La metaclase puede heredar de la metaclase, anular los métodos principales. Las metaclases incluso pueden usar metaclases.
  • Las subclases de una clase serán instancias de su metaclase si especificaste una metaclase-clase, pero no con una metaclase-función.
  • Puede estructurar mejor su código. Nunca usas metaclases para algo tan trivial como el ejemplo anterior. Suele ser por algo complicado. Tener la capacidad de crear varios métodos y agruparlos en una clase es muy útil para facilitar la lectura del código.
  • Usted puede conectar en __new__, __init__y __call__. Lo que te permitirá hacer cosas diferentes. Incluso si normalmente puedes hacerlo todo __new__, algunas personas simplemente se sienten más cómodas usándolo __init__.
  • Estas se llaman metaclases, ¡maldita sea! ¡Debe significar algo!

¿Por qué usarías metaclases?

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

Bueno, normalmente no lo haces:

Metaclasses are deeper magic that 99% of users should never worry about it. If you wonder whether you need them, you don't (the people who actually need them to know with certainty that they need them and don't need an explanation about why).

Profesor de Python Tim Peters

El caso de uso principal de una metaclase es la creación de una API. Un ejemplo típico de esto es el ORM de Django. Te permite definir algo como esto:

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

Pero si haces esto:

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

No devolverá un IntegerFieldobjeto. Devolverá un inte incluso puede tomarlo directamente de la base de datos.

Esto es posible porque models.Modeldefine __metaclass__y usa algo de magia que convertirá lo Personque 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 usar metaclases, recreando el código de esta API para hacer el trabajo real detrás de escena.

La última palabra

Primero, 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.

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

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

El 99% de las veces que necesita un cambio de clase, es mejor que lo use.

Pero el 98% de las veces, no necesitas ningún cambio de clase.

19
  • 43
    Parece que en Django models.Modelno usa __metaclass__sino class Model(metaclass=ModelBase):que hace referencia a una ModelBaseclase que luego hace la magia de metaclase antes mencionada. ¡Buena publicación! Aquí está la fuente de Django: github.com/django/django/blob/master/django/db/models/… 12/04/2017 a las 13:18
  • 22
    << Tenga cuidado aquí de que el __metaclass__atributo no se heredará, la metaclase del padre ( Bar.__class__) lo será. Si Barusó un __metaclass__atributo que creó Barcon type()(y no type.__new__()), las subclases no heredarán ese comportamiento. >> - ¿Podría usted / alguien explicar un poco más este pasaje?
    petrux
    25/04/2017 a las 21:32
  • 22
    @MaxGoodridge Esa es la sintaxis de Python 3 para metaclases. Consulte el modelo de datos de Python 3.6 VS el modelo de datos de Python 2.7
    TBBle
    13/06/2017 a las 13:22
  • 6
    Now you wonder why the heck is it written in lowercase, and not Type? - bueno, porque está implementado en C - es la misma razón por la que defaultdict está en minúsculas mientras que OrderedDict (en python 2) es CamelCase normal 29 de septiembre de 2017 a las 10:47
  • 21
    Es una respuesta wiki de la comunidad (por lo tanto, aquellos que comentaron con correcciones / mejoras podrían considerar editar sus comentarios en la respuesta, si están seguros de que son correctos). 8 de noviembre de 2017 a las 8:59
440

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

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 provienen el nombre, las bases y el dict en esta definición de clase de ejemplo.

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

Definamos una metaclase que demostrará cómo la llama ' clase: '.

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'

Y ahora, un ejemplo que realmente significa algo, esto automáticamente hará que las variables en la lista sean "atributos" configurados en la clase y configurados como 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 Initialisedgana al tener la metaclase init_attributesno se transmite a una subclase de Initialised.

Aquí hay un ejemplo aún más concreto, que muestra cómo puede subclase 'tipo' para hacer una metaclase que realiza 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
0
192

Otros han explicado cómo funcionan las metaclases y cómo encajan en el sistema de tipos de Python. A continuación, se muestra un ejemplo de para qué se pueden utilizar. En un marco de prueba que escribí, quería realizar un seguimiento del orden en el que se definieron las clases, para luego poder instanciarlas en este orden. Encontré que es 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

Todo lo que sea una subclase de MyTypeentonces obtiene un atributo de clase _orderque registra el orden en el que se definieron las clases.

5
  • Gracias por el ejemplo. ¿Por qué encontró esto más fácil que heredar de MyBase, quien __init__(self)dice type(self)._order = MyBase.counter; MyBase.counter += 1? 16/04/19 a las 17:59
  • 2
    Quería que las clases en sí mismas, no sus instancias, estuvieran numeradas.
    kindall
    16/04/19 a las 18:09
  • Bien, duh. Gracias. Mi código restablecería el atributo de MyType en cada instanciación y nunca establecería el atributo si nunca se crea una instancia de MyType. UPS. (Y una propiedad de clase también podría funcionar, pero a diferencia de la metaclase, no ofrece un lugar obvio para almacenar el contador). 17/04/19 a las 21:58
  • 1
    Este es un ejemplo muy interesante, sobre todo porque uno puede ver genuinamente por qué podría ser necesaria una metaclase con esto, para proporcionar una solución a una dificultad específica. OTOH Me cuesta convencerme de que alguien realmente necesitaría crear instancias de objetos en el orden en que se definieron sus clases: supongo que solo tenemos que confiar en tu palabra :). 22/10/19 a las 0:09
  • Era un marco de prueba de documentación y las clases eran descripciones declarativas de los archivos específicos que se probarían, las pruebas que se ejecutarían, etc. El marco informó los resultados de estos en un informe bien formateado agrupado por producto, documento y prueba. El informe era más útil si las pruebas se ejecutaban en un orden predecible. :-)
    kindall
    5 de mayo a las 20:57
178

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

Por ejemplo, si observa 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 Person están llenos de todo tipo de métodos útiles. Vea la fuente de algunas metaclases asombrosas.

1
  • 8
    ¿El uso de metaclases no agrega nuevas propiedades y métodos a una clase y no a una instancia? Por lo que entendí, la metaclase altera la clase en sí y, como resultado, las instancias pueden ser construidas de manera diferente por la clase alterada. Podría ser un poco engañoso para las personas que intentan obtener la naturaleza de una metaclase. Tener métodos útiles en instancias se puede lograr mediante la inherencia normal. Sin embargo, la referencia al código de Django como ejemplo es buena.
    trixn
    27 de enero de 2017 a las 23:24
138

Creo que la introducción de ONLamp a la programación de metaclase 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 (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 plan para la creación de una instancia, una metaclase es un plan 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 de modelo utilizan un enfoque de metaclase para permitir un estilo declarativo de escribir nuevos modelos o clases de formulario. Mientras la metaclase crea la clase, todos los miembros tienen la posibilidad de personalizar la clase en sí.

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

127

What are metaclasses? What do you use them for?

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

Pseudocódigo:

>>> Class(...)
instance

Lo anterior debería resultar familiar. Bueno, de donde Classviene? Es una instancia de una metaclase (también pseudocódigo):

>>> Metaclass(...)
Class

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

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

Poniéndolo de otra manera

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

    Cuando creamos una instancia de un objeto, obtenemos una instancia:

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

    Del mismo modo, cuando definimos una clase explícitamente con la metaclase predeterminada type, la instanciamos:

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

    >>> isinstance(object, type)
    True
    
  • En tercer lugar, una metaclase es la clase de una clase.

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

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

Así como podemos usar definiciones de clase para cambiar cómo se comportan las instancias de objetos personalizados, podemos usar una definición de clase de metaclase para cambiar la forma en que se comporta un objeto de clase.

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

The potential uses for metaclasses are boundless. Some ideas that have been explored include logging, interface checking, automatic delegation, automatic property creation, proxies, frameworks, and automatic resource locking/synchronization.

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

Usas una metaclase cada vez que creas una clase:

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

class Foo(object): 
    'demo'

Crea una instancia de un objeto de clase.

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

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

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

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

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

La metaclase del objeto que creamos, en ambos casos, es 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 __dict__y __weakref__están ahí porque no definimos __slots__? Si definimos__slots__ , ahorraremos un poco de espacio en las instancias, como podemos rechazarlos __dict__y __weakref__excluirlos. Por ejemplo:

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

... pero yo divago.)

Podemos extendernos typecomo cualquier otra definición de clase:

Aquí está el valor predeterminado __repr__de las clases:

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

Una de las cosas más valiosas que podemos hacer de forma predeterminada al escribir un objeto Python es proporcionarle un bien __repr__. Cuando llamamos help(repr), aprendemos que hay una buena prueba para un __repr__que también requiere una prueba de igualdad - obj == eval(repr(obj)). La siguiente implementación simple de __repr__y __eq__para instancias de clase de nuestra clase de tipo nos proporciona una demostración que puede mejorar el valor predeterminado __repr__de las clases:

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

Entonces, ahora, cuando creamos un objeto con esta metaclase, el __repr__eco en la línea de comando proporciona una vista mucho menos fea que la predeterminada:

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

Con una buena __repr__definición para la instancia de clase, tenemos una mayor capacidad para depurar nuestro código. Sin embargo, eval(repr(Class))es poco probable que se realicen muchas más comprobaciones con (ya que las funciones serían bastante imposibles de evaluar a partir de las predeterminadas __repr__).

Un uso esperado: __prepare__un espacio de nombres

Si, por ejemplo, queremos saber en qué orden se crean los métodos de una clase, podríamos proporcionar un dict ordenado como el espacio de nombres de la clase. Haríamos esto con lo __prepare__que devuelve el diccionario de espacio de nombres para la clase si está implementado en 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

Y ahora tenemos un registro del orden en que se crearon estos métodos (y otros atributos de clase):

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

Tenga en cuenta que este ejemplo se adaptó de la documentación : la nueva enumeración en la biblioteca estándar hace esto.

Entonces, lo que hicimos fue crear una instancia de una metaclase creando una clase. También podemos tratar la metaclase como lo haríamos con cualquier otra clase. Tiene un orden de resolución de método:

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

Y tiene aproximadamente el correcto repr(que ya no podemos evaluar a menos que podamos encontrar una manera de representar nuestras funciones):

>>> 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>})
94

Actualización de Python 3

Hay (en este punto) dos métodos clave en una metaclase:

  • __prepare__, y
  • __new__

__prepare__le permite proporcionar una asignación personalizada (como una OrderedDict) para usarla como espacio de nombres mientras se crea la clase. Debe devolver una instancia del espacio de nombres que elija. Si no implementa, se usa __prepare__un normal dict.

__new__ es responsable de la creación / modificación real de la clase final.

A una metaclase básica que no hace nada extra le gustaría:

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:

Supongamos que desea que se ejecute un código de validación simple en sus atributos, como si siempre tuviera que ser un into un str. Sin una metaclase, su clase se vería así:

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

Como puede ver, debe repetir el nombre del atributo dos veces. Esto hace posible los errores tipográficos junto con los errores irritantes.

Una metaclase simple puede abordar ese problema:

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

Así es como se vería la metaclase (sin usar __prepare__ya que no es necesaria):

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)

Una corrida de muestra de:

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

Nota : Este ejemplo es bastante simple y también podría haberse logrado con un decorador de clases, pero presumiblemente una metaclase real estaría haciendo mucho más.

La clase 'ValidateType' como referencia:

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
1
  • 1
    Tenga en cuenta que desde python 3.6, puede usar __set_name__(cls, name)en el descriptor ( ValidateType) para establecer el nombre en el descriptor ( self.namey en este caso también self.attr). Esto se agregó para no tener que sumergirse en metaclases para este caso de uso común específico (consulte PEP 487).
    Lars
    3 mar.20 a las 9:56
79

Papel del __call__()método de una metaclase al crear una instancia de clase

Si ha realizado programación en Python durante más de unos pocos meses, eventualmente se encontrará con un código que se ve así:

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

Esto último es posible cuando implementas el __call__()método mágico en la clase.

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

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

El __call__()método se invoca cuando se utiliza una instancia de una clase como invocable. Pero como hemos visto en respuestas anteriores, una clase en sí misma es una instancia de una metaclase, por lo que cuando usamos la clase como invocable (es decir, cuando creamos una instancia de ella) en realidad estamos llamando al __call__()método de su metaclase . En este punto, la mayoría de los programadores de Python están un poco confundidos porque les han dicho que al crear una instancia como esta instance = SomeClass(), estás llamando a su __init__()método. Algunos que han cavado un poco más profundo saben eso antes de que __init__()haya __new__(). Bueno, hoy se está revelando otra capa de verdad, antes de que __new__()exista la metaclase ' __call__().

Estudiemos la cadena de llamadas al método específicamente desde la perspectiva de crear una instancia de una clase.

Esta es una metaclase que registra exactamente el momento antes de que se cree una instancia y el momento en que está a punto de devolverla.

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 es una clase que usa esa metaclase

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

Y ahora creemos una instancia 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 el código anterior en realidad no hace nada más que registrar las tareas. Cada método delega el trabajo real a la implementación de su padre, manteniendo así el comportamiento predeterminado. Dado que typees Meta_1la clase principal de is ( typesiendo la metaclase principal predeterminada) y considerando la secuencia de ordenación de la salida anterior, ahora tenemos una pista sobre cuál sería la pseudo implementación 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 el __call__()método de la metaclase es el que se llama primero. Luego delega la creación de la instancia al __new__()método de la clase y la inicialización al de la instancia __init__(). También es el que finalmente devuelve la instancia.

De lo anterior se deriva que la metaclase __call__()también se da la oportunidad de decidir si es o no una llamada a Class_1.__new__()o Class_1.__init__()con el tiempo se hizo. En el transcurso de su ejecución, podría devolver un objeto que no haya sido tocado por ninguno de estos métodos. Tomemos, por ejemplo, este enfoque del patrón 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__()."

Observemos lo que sucede cuando intentamos repetidamente crear un objeto de 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
1
  • 1
    Esta es una buena adición a la "respuesta aceptada" previamente votada. Proporciona ejemplos para que los codificadores intermedios mastiquen. 26 nov 2019 a las 3:51
65

Una metaclase es una clase que indica cómo se debe crear (alguna) otra clase.

Este es un caso en el que vi la metaclase como una solución a mi problema: tenía un problema realmente complicado, que probablemente podría haberse resuelto de manera diferente, pero elegí resolverlo usando una metaclase. Debido a la complejidad, es uno de los pocos módulos que he escrito donde los comentarios en el módulo superan la cantidad de código que se ha escrito. Aquí 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()
0
52

La versión tl; dr

La type(obj)función le da el tipo de objeto.

El type()de una clase es su metaclase .

Para usar una metaclase:

class Foo(object):
    __metaclass__ = MyMetaClass

typees su propia metaclase. La clase de una clase es una metaclase: el cuerpo de una clase son los argumentos que se pasan a la metaclase que se utiliza para construir la clase.

Aquí puede leer sobre cómo usar metaclases para personalizar la construcción de clases.

50

typees en realidad una metaclass- una clase que crea otras clases. La mayoría metaclassson subclases de type. La metaclassrecibe la newclase como su primer argumento y proporcionan acceso a objeto de clase con detalles como se menciona a continuación:

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

Tenga en cuenta que no se creó una instancia de la clase en ningún momento; el simple acto de crear la clase desencadenó la ejecución del metaclass.

33

Las clases de Python son en sí mismas objetos, como en una instancia, de su metaclase.

La metaclase predeterminada, que se aplica cuando determina clases como:

class foo:
    ...

La metaclase se usa para aplicar alguna regla a un conjunto completo de clases. Por ejemplo, suponga que está creando un ORM para acceder a una base de datos y desea que los registros de cada tabla sean de una clase asignada a esa tabla (según los campos, las reglas comerciales, etc.), un posible uso de la metaclase es, por ejemplo, la lógica del grupo de conexiones, que comparten todas las clases de registros de todas las tablas. Otro uso es la lógica para admitir claves externas, lo que implica varias clases de registros.

cuando define metaclase, escribe una subclase y puede anular los siguientes métodos mágicos para insertar su 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 todos modos, esos dos son los ganchos más utilizados. la metaclasificación es poderosa, y la lista anterior no es ni cercana ni exhaustiva de usos para la metaclasificación.

27

La función type () puede devolver el tipo de un objeto o crear un nuevo tipo,

por ejemplo, podemos crear una clase Hi con la función type () y no es necesario usarla de esta manera con la clase Hi (objeto):

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

Además de usar type () para crear clases dinámicamente, puede controlar el comportamiento de creación de la clase y usar la metaclase.

Según el modelo de objetos de Python, la clase es el objeto, por lo que la clase debe ser una instancia de otra clase determinada. De forma predeterminada, una clase de Python es una instancia de la clase de tipo. Es decir, el tipo es la metaclase de la mayoría de las clases integradas y la metaclase de las clases definidas por el usuario.

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

La magia tendrá efecto cuando pasamos argumentos de palabras clave en la metaclase, indica al intérprete de Python que cree la CustomList a través de ListMetaclass. new (), en este punto, podemos modificar la definición de la clase, por ejemplo, y agregar un nuevo método y luego devolver la definición revisada.

18

Además de las respuestas publicadas, puedo decir que a metaclassdefine el comportamiento de una clase. Entonces, puede establecer explícitamente su metaclase. Siempre que Python obtiene una palabra clave class, comienza a buscar metaclass. Si no se encuentra, el tipo de metaclase predeterminado se usa para crear el objeto de la clase. Usando el __metaclass__atributo, puede establecer metaclasssu clase:

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

print(MyClass.__metaclass__)

Producirá la salida como esta:

class 'type'

Y, por supuesto, puede crear el suyo propio metaclasspara definir el comportamiento de cualquier clase que se cree utilizando su clase.

Para hacer eso, su metaclassclase de tipo predeterminada debe heredarse, ya que esta es la 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__)

La salida será:

class '__main__.MyMetaClass'
class 'type'
10

Tenga en cuenta que en Python 3.6 __init_subclass__(cls, **kwargs)se introdujo un nuevo método dunder para reemplazar muchos casos de uso comunes para metaclases. Se llama a Is cuando se crea una subclase de la clase definitoria. Consulte los documentos de Python .

8

En la programación orientada a objetos, una metaclase es una clase cuyas instancias son clases. Así como una clase ordinaria define el comportamiento de ciertos objetos, una metaclase define el comportamiento de cierta clase y sus instancias. El término metaclase simplemente significa algo que se usa para crear clases. En otras palabras, es la clase de una clase. La metaclase se usa para crear la clase, así que como si el objeto fuera una instancia de una clase, una clase es una instancia de una metaclase. En Python las clases también se consideran objetos.

2
  • 2
    En lugar de dar definiciones librescas, hubiera sido mejor si hubiera agregado algunos ejemplos. La primera línea de su respuesta parece haber sido copiada de la entrada de Wikipedia de Metaclasses. 13/07/19 a las 17:41
  • 1
    @verisimilitude También estoy aprendiendo, ¿puedes ayudarme a mejorar esta respuesta proporcionando algunos ejemplos prácticos de tu experiencia? 15/07/19 a las 6:02
8

Aquí hay otro ejemplo de para qué se puede usar:

  • Puede usar metaclasspara cambiar la función de su instancia (la clase).
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()

El metaclasses poderoso, hay muchas cosas (como la magia de los monos) que puedes hacer con él, pero ten cuidado, es posible que solo tú lo sepas.

6

Una clase, en Python, es un objeto y, como cualquier otro objeto, es una instancia de "algo". Este "algo" es lo que se denomina Metaclase. Esta metaclase es un tipo especial de clase que crea objetos de otra clase. Por lo tanto, la metaclase es responsable de crear nuevas clases. Esto permite al programador personalizar la forma en que se generan las clases.

Para crear una metaclase, se suelen anular los métodos new () e init (). new () se puede anular para cambiar la forma en que se crean los objetos, mientras que init () se puede anular para cambiar la forma de inicializar el objeto. La metaclase se puede crear de varias formas. Una de las formas es usar la función type (). La función type (), cuando se llama con 3 parámetros, crea una metaclase. Los parámetros son: -

  1. Nombre de la clase
  2. Tupla que tiene clases base heredadas por clase
  3. Un diccionario que tiene todos los métodos de clase y variables de clase.

Otra forma de crear una metaclase comprende la palabra clave 'metaclase'. Defina la metaclase como una clase simple. En los parámetros de la clase heredada, pase metaclass = metaclass_name

La metaclase se puede utilizar específicamente en las siguientes situaciones: -

  1. cuando un efecto particular debe aplicarse a todas las subclases
  2. Se requiere cambio automático de clase (en la creación)
  3. Por desarrolladores de API
3

En Python, una metaclase es una subclase de una subclase que determina cómo se comporta una subclase. Una clase es una instancia de otra metaclase. En Python, una clase especifica cómo se comportará la instancia de la clase.

Dado que las metaclases están a cargo de la generación de clases, puede escribir sus propias metaclases personalizadas para cambiar la forma en que se crean las clases realizando acciones adicionales o inyectando código. Las metaclases personalizadas no siempre son importantes, pero pueden serlo.

3

Vi un caso de uso interesante para metaclases en un paquete llamado classutilities. Comprueba si todas las variables de clase están en formato de mayúsculas (es conveniente tener lógica unificada para las clases de configuración) y comprueba si no hay métodos a nivel de instancia en la clase. Otro ejemplo interesante de metaclasas fue la desactivación de pruebas unitarias basadas en condiciones complejas (comprobación de valores de múltiples variables ambientales).

-3

Las metaclases no son más que una simple clase interna de su clase modelo, por ejemplo: en Django o python

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    class Meta:
        abstract = True

Aquí, si abstract = True, este modelo será una clase base abstracta. Metaclass cambiará el comportamiento de su clase base.

1
  • Esta es una característica específica de Django donde una clase anidada llamada Metatiene un significado especial. La pregunta es sobre una característica del lenguaje Python no relacionada con un nombre similar.
    DavidW
    el dia de ayer
-10

La metaclase es un tipo de clase que define cómo se comportará la clase o podemos decir que una clase es en sí misma una instancia de una metaclase.

1
  • 1
    Agregue información relevante ... sus comentarios son confusos
    swastik
    1 nov.20 a las 18:39