Crear metaclases personalizadas
En Python, podemos personalizar el proceso de creación de clases pasando la palabra clave metaclass en la definición de la clase. También puede hacerse heredando de una clase que ya haya definido esta palabra clave.
class MyMeta(type):
pass
class MyClass(metaclass=MyMeta):
pass
class MySubclass(MyClass):
pass
Como puedes ver abajo, el tipo de la clase MyMeta es type y el tipo de MyClass y MySubClass es MyMeta.
print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))
<class 'type'>
<class '__main__.MyMeta'>
<class '__main__.MyMeta'>
Si al definir una clase no se especifica metaclase, se usará por defecto la metaclase type. Si se indica una metaclase y no es una instancia de type(), entonces se usa directamente como metaclase.
__new__ y __init__
Las metaclases también pueden definirse de dos formas, como se muestra a continuación. Explicamos la diferencia enseguida.
class MetaOne(type):
def __new__(cls, name, bases, dict):
pass
class MetaTwo(type):
def __init__(self, name, bases, dict):
pass
__new__ se usa cuando quieres definir el diccionario o la tupla de bases antes de que se cree la clase. El valor de retorno de __new__ suele ser una instancia de cls. __new__ permite a las subclases de tipos inmutables personalizar la creación de instancias y puede sobreescribirse en metaclases personalizadas para ajustar la creación de clases. __init__ se llama normalmente después de que el objeto se haya creado para inicializarlo.
Método __call__ de la metaclase
Según la documentación oficial, también podemos sobreescribir otros métodos de clase definiendo un método __call__() personalizado en la metaclase que permita un comportamiento propio cuando se llama a la clase.
Método __prepare__ de la metaclase
Según el modelo de datos de Python
Una vez identificada la metaclase adecuada, se prepara el espacio de nombres de la clase. Si la metaclase tiene un atributo __prepare__, se llama como namespace = metaclass.__prepare__(name, bases, **kwds) (donde los argumentos con nombre adicionales, si los hay, provienen de la definición de la clase). Si la metaclase no tiene atributo __prepare__, el espacio de nombres de la clase se inicializa como una asignación ordenada vacía. - docs.python.org
Patrón singleton con una metaclase
Es un patrón de diseño que restringe la instanciación de una clase a un único objeto. Puede ser útil, por ejemplo, al diseñar una clase de conexión a base de datos: quizá quieras tener una sola instancia de la clase de conexión.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonMeta,cls).__call__(*args, **kwargs)
return cls._instances[cls]
class SingletonClass(metaclass=SingletonMeta):
pass
Conclusión
En este artículo has aprendido qué son las metaclases y cómo implementarlas en tus programas en Python. Las metaclases pueden aplicarse, entre otros usos, al registro de clases en el momento de su creación, al registro de logs y al perfilado. Son conceptos bastante abstractos y quizá te preguntes si realmente necesitas usarlos. El veterano pythonista Tim Peters lo resume muy bien.
"Las metaclases son una magia más profunda de la que el 99% de los usuarios debería preocuparse. Si te preguntas si las necesitas, no las necesitas (las personas que realmente las necesitan lo saben con certeza y no necesitan que se les explique por qué)." - realpython
Si quieres seguir aprendiendo Python, empieza el itinerario de habilidades Python Programming de DataCamp y echa un vistazo a nuestro tutorial sobre clases en Python.
Una metaclase en Python es la clase de una clase: define cómo se comporta una clase. Una clase, a su vez, es una instancia de una metaclase. En Python, una clase define cómo se comportarán las instancias de esa clase. Para entender bien las metaclases, conviene tener experiencia previa trabajando con clases en Python. Antes de profundizar, despejemos algunos conceptos.
En Python, todo es un objeto
Las clases en Python se pueden crear dinámicamente
typeen Python nos permite conocer el tipo de un objeto. Probemos con el objeto que acabamos de crear.Espera, ¿qué ha pasado? Podríamos esperar que el tipo del objeto que creamos arriba fuese class, pero no lo es. Quédate con esa idea un momento; lo veremos en un instante. También observamos que el tipo de
typeestype. Es una instancia detype. Otra cosa "mágica" que hacetypees permitirnos crear clases dinámicamente. Veamos cómo. La claseDataCampde abajo podría crearse así usandotype:En el ejemplo anterior,
DataCampes el nombre de la clase, mientras queDataCampClasses la variable que guarda la referencia a la clase. Al usartypepodemos pasar atributos de la clase mediante un diccionario, como se muestra a continuación:Si quisiéramos que nuestra
PythonClassheredase de la claseDataCamp, la pasamos como segundo argumento al definir la clase contype:Con estos dos conceptos claros, vemos que Python crea las clases usando una metaclase. Ya hemos dicho que todo en Python es un objeto; esos objetos los crean metaclases. Cada vez que usamos
classpara crear una clase, hay una metaclase que hace el trabajo entre bambalinas. Ya hemos visto atypehacerlo en la práctica. Es parecido astr, que crea cadenas, eint, que crea enteros. En Python, el atributo___class__nos permite comprobar el tipo de la instancia actual. Creamos una cadena y miramos su tipo:También podemos consultarlo con
type(article).Si comprobamos el tipo de
str, veremos que también estype.Si comprobamos el tipo de
float,int,list,tupleydict, obtendremos un resultado similar. Esto es porque todos estos objetos son de tipotype.Ya hemos visto que
typecrea clases. Por tanto, si miramos el__class__de__class__, debería devolvertype.