Tengo un amigo al que le gusta usar metaclases y regularmente las ofrece como solución.
Soy de la opinión de que casi nunca es necesario utilizar metaclases. ¿Por qué? porque me imagino que si estás haciendo algo así en una clase, probablemente deberías hacerlo con un objeto. Y es necesario un pequeño rediseño / refactorización.
Ser capaz de usar metaclases ha provocado que mucha gente en muchos lugares use las clases como una especie de objeto de segunda categoría, lo que me parece desastroso. ¿Se reemplazará la programación por la metaprogramación? Lamentablemente, la incorporación de decoradores de clase lo ha hecho aún más aceptable.
Entonces, por favor, estoy desesperado por conocer sus casos de uso válidos (concretos) para metaclases en Python. O para saber por qué las clases mutantes son mejores que los objetos mutantes, a veces.
Comenzaré:
A veces, cuando se usa una biblioteca de terceros, es útil poder mutar la clase de cierta manera.
(Este es el único caso en el que puedo pensar, y no es concreto)
Respuestas:
Tengo una clase que maneja el trazado no interactivo, como interfaz para Matplotlib. Sin embargo, en ocasiones uno quiere hacer una trama interactiva. Con solo un par de funciones, descubrí que podía incrementar el recuento de figuras, llamar a dibujar manualmente, etc., pero necesitaba hacer esto antes y después de cada llamada de trazado. Entonces, para crear tanto una envoltura de trazado interactivo como una envoltura de trazado fuera de la pantalla, descubrí que era más eficiente hacer esto a través de metaclases, envolviendo los métodos apropiados, que hacer algo como:
Este método no se mantiene al día con los cambios de la API y demás, pero uno que itera sobre los atributos de la clase
__init__
antes de volver a configurar los atributos de la clase es más eficiente y mantiene las cosas actualizadas:Por supuesto, puede haber mejores formas de hacer esto, pero he descubierto que esto es efectivo. Por supuesto, esto también se podría hacer en
__new__
o__init__
, pero esta fue la solución que encontré más sencilla.fuente
Recientemente me hicieron la misma pregunta y encontré varias respuestas. Espero que esté bien revivir este hilo, ya que quería desarrollar algunos de los casos de uso mencionados y agregar algunos nuevos.
La mayoría de las metaclases que he visto hacen una de dos cosas:
Registro (agregando una clase a una estructura de datos):
Siempre que subclases
Model
, tu clase se registra en elmodels
diccionario:Esto también se puede hacer con los decoradores de clases:
O con una función de registro explícita:
En realidad, esto es más o menos lo mismo: mencionas a los decoradores de clases de manera desfavorable, pero en realidad no es más que azúcar sintáctico para la invocación de una función en una clase, por lo que no hay magia en ello.
De todos modos, la ventaja de las metaclases en este caso es la herencia, ya que funcionan para cualquier subclases, mientras que las otras soluciones solo funcionan para subclases explícitamente decoradas o registradas.
Refactorización (modificando atributos de clase o agregando nuevos):
Siempre que subclases
Model
y defines algunosField
atributos, se inyectan con sus nombres (para mensajes de error más informativos, por ejemplo) y se agrupan en un_fields
diccionario (para una fácil iteración, sin tener que revisar todos los atributos de clase y todas sus clases base ' atributos cada vez):Nuevamente, esto se puede hacer (sin herencia) con un decorador de clases:
O explícitamente:
Aunque, al contrario de su defensa de la programación no metabólica legible y mantenible, esto es mucho más engorroso, redundante y propenso a errores:
Habiendo considerado los casos de uso más comunes y concretos, los únicos casos en los que absolutamente TIENE que usar metaclases son cuando desea modificar el nombre de la clase o la lista de clases base, porque una vez definidos, estos parámetros se incorporan a la clase y no al decorador. o la función puede deshacerlos.
Esto puede ser útil en marcos para emitir advertencias siempre que se definan clases con nombres similares o árboles de herencia incompletos, pero no puedo pensar en una razón además de trollear para cambiar estos valores. Quizás David Beazley pueda.
De todos modos, en Python 3, las metaclases también tienen el
__prepare__
método, que le permite evaluar el cuerpo de la clase en un mapeo que no sea adict
, por lo que admite atributos ordenados, atributos sobrecargados y otras cosas interesantes y perversas:Puede argumentar que los atributos ordenados se pueden lograr con contadores de creación y la sobrecarga se puede simular con argumentos predeterminados:
Además de ser mucho más feo, también es menos flexible: ¿qué pasa si quieres atributos literales ordenados, como enteros y cadenas? ¿Y si
None
es un valor válido parax
?He aquí una forma creativa de resolver el primer problema:
Y aquí hay una forma creativa de resolver el segundo:
Pero esto es mucho, MUCHO vudú que una simple metaclase (especialmente la primera, que realmente derrite tu cerebro). Mi punto es que usted ve las metaclases como algo desconocido y contrario a la intuición, pero también puede verlas como el siguiente paso de evolución en los lenguajes de programación: solo tiene que ajustar su forma de pensar. Después de todo, probablemente podría hacer todo en C, incluida la definición de una estructura con punteros de función y pasarla como primer argumento a sus funciones. Una persona que vea C ++ por primera vez podría decir: "¿Qué es esta magia? ¿Por qué el compilador pasa implícitamente
this
a los métodos, pero no a las funciones regulares y estáticas? Es mejor ser explícito y detallado sobre tus argumentos ". Pero entonces, la programación orientada a objetos es mucho más poderosa una vez que la obtienes; y también lo es, eh ... la programación casi orientada a aspectos, supongo. Y una vez que entender las metaclases, en realidad son muy simples, así que ¿por qué no usarlas cuando sea conveniente?Y, finalmente, las metaclases son fantásticas y la programación debería ser divertida. Usar construcciones de programación estándar y patrones de diseño todo el tiempo es aburrido y poco inspirador, y obstaculiza su imaginación. ¡Vive un poco! Aquí tienes una metametaclase, solo para ti.
Editar
Esta es una pregunta bastante antigua, pero todavía está recibiendo votos positivos, así que pensé en agregar un enlace a una respuesta más completa. Si desea leer más sobre las metaclases y sus usos, acabo de publicar un artículo al respecto aquí .
fuente
__metaclass__
atributo, pero este atributo ya no es especial en Python 3. ¿Hay alguna forma de hacer que esto "las clases secundarias también son construidas por la metaclase de los padres" funcione en Python 3 ?init_subclass
, por lo que ahora puede manipular subclases en una clase base y ya no necesita una metaclase para ese propósito.El propósito de las metaclases no es reemplazar la distinción clase / objeto con metaclase / clase, es cambiar el comportamiento de las definiciones de clase (y por lo tanto sus instancias) de alguna manera. Efectivamente, es para alterar el comportamiento de la declaración de clase de maneras que pueden ser más útiles para su dominio particular que el predeterminado. Las cosas para las que los he usado son:
Seguimiento de subclases, generalmente para registrar controladores. Esto es útil cuando se usa una configuración de estilo de complemento, donde desea registrar un controlador para una cosa en particular simplemente subclasificando y configurando algunos atributos de clase. p.ej. suponga que escribe un controlador para varios formatos de música, donde cada clase implementa los métodos apropiados (reproducir / obtener etiquetas, etc.) para su tipo. Agregar un controlador para un nuevo tipo se convierte en:
Luego, la metaclase mantiene un diccionario de
{'.mp3' : MP3File, ... }
etc, y construye un objeto del tipo apropiado cuando solicita un controlador a través de una función de fábrica.Cambio de comportamiento. Es posible que desee asignar un significado especial a ciertos atributos, lo que resulta en un comportamiento alterado cuando están presentes. Por ejemplo, es posible que desee buscar métodos con el nombre
_get_foo
y_set_foo
convertirlos de forma transparente en propiedades. Como ejemplo del mundo real, aquí hay una receta que escribí para dar más definiciones de estructuras similares a C. La metaclase se usa para convertir los elementos declarados en una cadena de formato de estructura, manejando la herencia, etc., y producir una clase capaz de lidiar con ella.Para ver otros ejemplos del mundo real, eche un vistazo a varios ORM, como el ORM de sqlalchemy o sqlobject . Nuevamente, el propósito es interpretar definiciones (aquí definiciones de columna SQL) con un significado particular.
fuente
Comencemos con la cita clásica de Tim Peter:
Habiendo dicho eso, me he encontrado (periódicamente) con verdaderos usos de las metaclases. El que me viene a la mente es en Django, donde todos sus modelos heredan de los modelos. Model, a su vez, hace algo de magia seria para envolver sus modelos DB con la bondad ORM de Django. Esa magia ocurre por medio de metaclases. Crea todo tipo de clases de excepción, clases de administrador, etc., etc.
Vea django / db / models / base.py, class ModelBase () para el comienzo de la historia.
fuente
Las metaclases pueden ser útiles para la construcción de lenguajes específicos de dominio en Python. Ejemplos concretos son Django, la sintaxis declarativa de esquemas de base de datos de SQLObject.
Un ejemplo básico de A Conservative Metaclass por Ian Bicking:
Algunas otras técnicas: Ingredientes para construir un DSL en Python (pdf).
Editar (por Ali): Un ejemplo de cómo hacer esto usando colecciones e instancias es lo que preferiría. El hecho importante son las instancias, que le dan más poder y eliminan la razón para usar metaclases. Además, vale la pena señalar que su ejemplo usa una mezcla de clases e instancias, lo que seguramente es una indicación de que no puede hacerlo todo con metaclases. Y crea una forma verdaderamente no uniforme de hacerlo.
No es perfecto, pero ya casi no hay magia, no hay necesidad de metaclases y una uniformidad mejorada.
fuente
Un patrón razonable de uso de la metaclase es hacer algo una vez cuando se define una clase en lugar de repetidamente cuando se crea una instancia de la misma clase.
Cuando varias clases comparten el mismo comportamiento especial, la repetición
__metaclass__=X
es obviamente mejor que repetir el código de propósito especial y / o introducir superclases compartidas ad-hoc.Pero incluso con solo una clase especial y sin extensión previsible,
__new__
y__init__
de una metaclase son una forma más limpia de inicializar variables de clase u otros datos globales que entremezclar código de propósito especialdef
yclass
declaraciones normales y en el cuerpo de definición de clase.fuente
La única vez que usé metaclases en Python fue cuando escribí un contenedor para la API de Flickr.
Mi objetivo era raspar el sitio de la API de flickr y generar dinámicamente una jerarquía de clases completa para permitir el acceso a la API utilizando objetos Python:
Entonces, en ese ejemplo, debido a que generé toda la API de Python Flickr desde el sitio web, realmente no conozco las definiciones de clase en tiempo de ejecución. Ser capaz de generar tipos dinámicamente fue muy útil.
fuente
Estaba pensando lo mismo ayer y estoy completamente de acuerdo. Las complicaciones en el código causadas por los intentos de hacerlo más declarativo generalmente hacen que el código base sea más difícil de mantener, más difícil de leer y menos pitónico en mi opinión. También normalmente requiere mucho copy.copy () ing (para mantener la herencia y copiar de una clase a otra) y significa que tienes que buscar en muchos lugares para ver qué está pasando (siempre mirando desde la metaclase hacia arriba) que va en contra de la grano de pitón también. He estado revisando formencode y sqlalchemy code para ver si un estilo tan declarativo valía la pena y claramente no. Dicho estilo debe dejarse a los descriptores (como propiedades y métodos) y datos inmutables. Ruby tiene un mejor soporte para tales estilos declarativos y me alegro que el lenguaje central de Python no vaya por ese camino.
Puedo ver su uso para depurar, agregar una metaclase a todas sus clases base para obtener información más rica. También veo su uso solo en proyectos (muy) grandes para deshacerse de algún código repetitivo (pero con la pérdida de claridad). sqlalchemy, por ejemplo , los usa en otros lugares, para agregar un método personalizado particular a todas las subclases en función de un valor de atributo en su definición de clase, por ejemplo, un ejemplo de juguete
podría tener una metaclase que generara un método en esa clase con propiedades especiales basadas en "hola" (digamos un método que agregó "hola" al final de una cadena). Podría ser bueno para la capacidad de mantenimiento asegurarse de que no tiene que escribir un método en cada subclase que crea, sino que todo lo que tiene que definir es method_maker_value.
Sin embargo, la necesidad de esto es tan rara y solo reduce un poco la escritura que no vale la pena considerarla a menos que tenga una base de código lo suficientemente grande.
fuente
Nunca es absolutamente necesario usar una metaclase, ya que siempre puede construir una clase que haga lo que desee utilizando la herencia o agregación de la clase que desea modificar.
Dicho esto, puede ser muy útil en Smalltalk y Ruby poder modificar una clase existente, pero a Python no le gusta hacerlo directamente.
Hay un excelente artículo de DeveloperWorks sobre metaclases en Python que podría ayudar. El artículo de Wikipedia también es bastante bueno.
fuente
Algunas bibliotecas GUI tienen problemas cuando varios subprocesos intentan interactuar con ellos.
tkinter
es uno de esos ejemplos; y si bien uno puede manejar explícitamente el problema con eventos y colas, puede ser mucho más simple usar la biblioteca de una manera que ignore el problema por completo. He aquí la magia de las metaclases.Ser capaz de reescribir dinámicamente una biblioteca completa sin problemas para que funcione correctamente como se espera en una aplicación multiproceso puede ser extremadamente útil en algunas circunstancias. El módulo safetkinter hace eso con la ayuda de una metaclase proporcionada por el threadbox módulo : no se necesitan eventos ni colas.
Un aspecto
threadbox
interesante de es que no le importa de qué clase clone. Proporciona un ejemplo de cómo todas las clases base pueden ser tocadas por una metaclase si es necesario. Un beneficio adicional que viene con las metaclases es que también se ejecutan en clases heredadas. Programas que se escriben solos, ¿por qué no?fuente
El único caso de uso legítimo de una metaclase es evitar que otros desarrolladores entrometidos toquen su código. Una vez que un desarrollador entrometido domina las metaclases y comienza a hurgar en las tuyas, agrega otro nivel o dos para mantenerlos fuera. Si eso no funciona, comience a usar
type.__new__
o quizás algún esquema usando una metaclase recursiva.(escrito en broma, pero he visto este tipo de ofuscación. Django es un ejemplo perfecto)
fuente
¡Las metaclases no están reemplazando a la programación! Son solo un truco que puede automatizar o hacer más elegantes algunas tareas. Un buen ejemplo de esto es la biblioteca de resaltado de sintaxis de Pygments . Tiene una clase llamada
RegexLexer
que permite al usuario definir un conjunto de reglas de lexing como expresiones regulares en una clase. Se utiliza una metaclase para convertir las definiciones en un analizador útil.Son como la sal; es fácil de usar demasiado.
fuente
La forma en que usé las metaclases fue proporcionar algunos atributos a las clases. Toma por ejemplo:
pondrá el atributo de nombre en cada clase que tendrá la metaclase establecida para apuntar a NameClass.
fuente
Este es un uso menor, pero ... una cosa que he encontrado útil para las metaclases es invocar una función cada vez que se crea una subclase. Codifiqué esto en una metaclase que busca un
__initsubclass__
atributo: cada vez que se crea una subclase, todas las clases principales que definen ese método se invocan con__initsubclass__(cls, subcls)
. Esto permite la creación de una clase principal que luego registra todas las subclases con algún registro global, ejecuta comprobaciones invariantes en las subclases siempre que se definen, realiza operaciones de vinculación tardía, etc. todo sin tener que llamar manualmente a funciones o crear metaclases personalizadas que realizar cada una de estas funciones por separado.Eso sí, poco a poco me he dado cuenta de que la magia implícita de este comportamiento es algo indeseable, ya que es inesperado si se mira una definición de clase fuera de contexto ... y por eso he dejado de usar esa solución para cualquier cosa más seria inicializando un
__super
atributo para cada clase e instancia.fuente
Recientemente tuve que usar una metaclase para ayudar a definir declarativamente un modelo SQLAlchemy alrededor de una tabla de base de datos poblada con datos del censo de EE. UU. De http://census.ire.org/data/bulkdata.html
IRE proporciona shells de bases de datos para las tablas de datos del censo, que crean columnas enteras siguiendo una convención de nomenclatura de la Oficina del Censo de p012015, p012016, p012017, etc.
Quería a) poder acceder a estas columnas usando una
model_instance.p012017
sintaxis, b) ser bastante explícito sobre lo que estaba haciendo yc) no tener que definir explícitamente docenas de campos en el modelo, así que subclasé SQLAlchemyDeclarativeMeta
para iterar a través de un rango de las columnas y crea automáticamente campos modelo correspondientes a las columnas:Luego podría usar esta metaclase para la definición de mi modelo y acceder a los campos enumerados automáticamente en el modelo:
fuente
Parece que aquí se describe un uso legítimo : reescribir cadenas de documentos de Python con una metaclase.
fuente
Tuve que usarlos una vez para un analizador binario para que fuera más fácil de usar. Defina una clase de mensaje con atributos de los campos presentes en el cable. Debían ordenarse de la forma en que se declararon para construir el formato de cable final a partir de él. Puede hacer eso con metaclases, si usa un dict de espacio de nombres ordenado. De hecho, está en los ejemplos de Metaclases:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
Pero en general: evalúe con mucho cuidado, si realmente necesita la complejidad adicional de las metaclases.
fuente
la respuesta de @Dan Gittik es genial
los ejemplos al final podrían aclarar muchas cosas, lo cambié a python 3 y doy alguna explicación:
fuente
Otro caso de uso es cuando desea poder modificar atributos de nivel de clase y asegurarse de que solo afecte al objeto en cuestión. En la práctica, esto implica "fusionar" las fases de las metaclases y las instancias de clases, lo que le lleva a tratar solo con instancias de clase de su propio tipo (único).
También tuve que hacer eso cuando (por cuestiones de legibilidad y polimorfismo ) queríamos definir dinámicamente
property
s cuyos valores devueltos (pueden) resultar de cálculos basados en (a menudo cambiantes) atributos de nivel de instancia, que solo se pueden hacer a nivel de clase , es decir , después de la instanciación de la metaclase y antes de la instanciación de la clase.fuente