in

oop – ¿Qué son las metaclases en Python?

apple touch icon@2

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 use la palabra clave class, Python lo 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 es por eso que es una clase..

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

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

p.ej:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute="foo" # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

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 el class palabra 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.

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

Bien, 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 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 principal (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 MyShinyClass como nombre de la clase y como variable para contener la referencia de clase. Pueden ser diferentes, pero no hay razón para complicar las cosas.

type acepta un diccionario para definir los atributos de la clase. 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 usas la palabra clave classy lo hace utilizando una metaclase.

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

Tu has visto eso type te permite hacer algo como esto:

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

Es porque la función type es de hecho una metaclase. type es 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 str, la clase que crea objetos de cadenas, y int la clase que crea objetos enteros. type es solo la clase que crea objetos de clase.

Ves que al comprobar 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, ¿cuál es el __class__ de cualquier __class__ ?

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

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

Puede llamarlo una ‘fábrica de clases’ si lo desea.

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

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.

Usted escribe class Foo(object) primero, pero el objeto de clase Foo aún no 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 lo hace, utilizará
type para crear la clase.

Léelo varias veces.

Cuando tu lo hagas:

class Foo(Bar):
    pass

Python hace lo siguiente:

Hay un __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 Foo usando lo que hay en __metaclass__.

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

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

Tenga cuidado aquí de que el __metaclass__ El atributo no se heredará, la metaclase del padre (Bar.__class__) estarán. Si Bar uso un __metaclass__ atributo que creó Bar con 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.

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

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

es decir, el __metaclass__ El 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 en gran parte lo mismo.

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.

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 configurar __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, __metaclass__ en 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.
   ...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Python: acceso a la base de datos MySQL

gfg 200x200 min

¿Cómo conectar un enrutador a otro para expandir la red?