¿Cuáles son algunos casos de uso (concretos) para las metaclases?

118

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)

Ali Afshar
fuente
3
Esta es una gran pregunta. A juzgar por las respuestas a continuación, está bastante claro que no existe un uso concreto para las metaclases.
Marcus Ottosson

Respuestas:

25

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:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

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:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

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.

Mate
fuente
103

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:

  1. Registro (agregando una clase a una estructura de datos):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Siempre que subclases Model, tu clase se registra en el modelsdiccionario:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}

    Esto también se puede hacer con los decoradores de clases:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass

    O con una función de registro explícita:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)

    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.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
  2. Refactorización (modificando atributos de clase o agregando nuevos):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass

    Siempre que subclases Modely defines algunos Fieldatributos, se inyectan con sus nombres (para mensajes de error más informativos, por ejemplo) y se agrupan en un _fieldsdiccionario (para una fácil iteración, sin tener que revisar todos los atributos de clase y todas sus clases base ' atributos cada vez):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}

    Nuevamente, esto se puede hacer (sin herencia) con un decorador de clases:

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(

    O explícitamente:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!

    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:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}

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.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

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 a dict, por lo que admite atributos ordenados, atributos sobrecargados y otras cosas interesantes y perversas:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>

Puede argumentar que los atributos ordenados se pueden lograr con contadores de creación y la sobrecarga se puede simular con argumentos predeterminados:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

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 Nonees un valor válido para x?

He aquí una forma creativa de resolver el primer problema:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

Y aquí hay una forma creativa de resolver el segundo:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

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ícitamentethisa 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.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan

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í .

Dan Gittik
fuente
5
Eso es una gran respuesta, gracias por el tiempo escribiendo y dando varios ejemplos
Chen A.
"... la ventaja de las metaclases en este caso es la herencia, ya que funcionan para cualquier subclase", supongo que no en Python 3. Creo que funciona en Python 2 solo porque cualquier clase secundaria hereda el __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 ?
ForceBru
2
Esto también es cierto para Python 3, porque una clase B, heredada de A, cuya metaclase es M, también es un tipo de M. Entonces, cuando se evalúa B, se invoca M para crearlo, y esto efectivamente le permite para "trabajar en cualquier subclases" (de A). Habiendo dicho eso, Python 3.6 introdujo el mucho más simple init_subclass, por lo que ahora puede manipular subclases en una clase base y ya no necesita una metaclase para ese propósito.
Dan Gittik
Esto es brillante, leí tantas publicaciones de blog sobre metaclases, solo esta hace conocer los pros, los contras y las alternativas a la metaclase.
ospider
36

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:

    class Mp3File(MusicFile):
        extensions = ['.mp3']  # Register this type as a handler for mp3 files
        ...
        # Implementation of mp3 methods go here

    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_fooy _set_fooconvertirlos 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.

Brian
fuente
3
Bueno, sí, rastreando subclases. Pero, ¿por qué querrías eso? Su ejemplo está implícito para register_music_file (Mp3File, ['.mp3']), y la forma explícita es más legible y fácil de mantener. Este es un ejemplo de los casos malos de los que estoy hablando.
Ali Afshar
Sobre el caso de ORM, ¿estás hablando de la forma basada en clases de definir tablas, o las metaclases en objetos mapeados? Porque SQLAlchemy puede (correctamente) mapear a cualquier clase (y supongo que no usa una metaclase para esa actividad).
Ali Afshar
10
Prefiero el estilo más declarativo, en lugar de requerir métodos de registro adicionales para cada subclase, mejor si todo está envuelto en una sola ubicación.
Brian
Para sqlalchemy, estoy pensando principalmente en la capa declarativa, por lo que quizás sqlobject sea un mejor ejemplo. Sin embargo, las metaclases utilizadas internamente también son ejemplos de reinterpretación similar de atributos particulares para declarar significado.
Brian
2
Lo siento, uno de mis comentarios se perdió en el escenario de tiempo de espera de SO. Encuentro las clases de declarativo casi una abominación. Sé que a la gente le encanta y es un comportamiento aceptado. Pero (por experiencia) sé que es inutilizable en una situación en la que quieres UN-declare cosas. Dar de baja una clase es difícil .
Ali Afshar
17

Comencemos con la cita clásica de Tim Peter:

Las metaclases son una magia más profunda de la que el 99% de los usuarios deberían preocuparse. Si se pregunta si los necesita, no los necesita (las personas que realmente los necesitan saben con certeza que los necesitan y no necesitan una explicación de por qué). Tim Peters (publicación clp 2002-12-22)

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.

Peter Rowell
fuente
Bueno, sí, veo el punto. No me pregunto "cómo" o "por qué" usar metaclases, me pregunto el "quién" y el "qué". Los ORM son un caso común aquí, veo. Desafortunadamente, el ORM de Django es bastante pobre en comparación con SQLAlchemy, que tiene menos magia. La magia es mala y las metaclases realmente no son necesarias para esto.
Ali Afshar
9
Habiendo leído la cita de Tim Peters en el pasado, el tiempo ha demostrado que su declaración es bastante inútil. No fue hasta que investigando las metaclases de Python aquí en StackOverflow que se hizo evidente cómo implementarlas. Después de obligarme a aprender a escribir y usar metaclases, sus habilidades me asombraron y me dieron una mejor comprensión de cómo funciona Python realmente. Las clases pueden proporcionar código reutilizable y las metaclases pueden proporcionar mejoras reutilizables para esas clases.
Noctis Skytower
6

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:

Las metaclases que he usado han sido principalmente para soportar una especie de estilo declarativo de programación. Por ejemplo, considere un esquema de validación:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

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.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

No es perfecto, pero ya casi no hay magia, no hay necesidad de metaclases y una uniformidad mejorada.

jfs
fuente
Gracias por esto. Este es un muy buen ejemplo de un caso de uso que creo que es innecesario, feo e inmanejable, que sería más simple basado en una instancia de colección simple (con colecciones anidadas según sea necesario).
Ali Afshar
1
@Ali A: puede proporcionar un ejemplo concreto de comparación lado a lado entre la sintaxis declarativa a través de metaclases y un enfoque basado en una instancia de colección simple.
jfs
@Ali A: puede editar mi respuesta en el lugar para agregar un ejemplo de estilo de colección.
jfs
Ok hecho eso. Lo siento, tengo un poco de prisa hoy, pero intentaré responder a cualquier consulta más tarde / mañana. ¡Felices vacaciones!
Ali Afshar
2
El segundo ejemplo es feo ya que tuvo que vincular la instancia del validador con su nombre. Una forma ligeramente mejor de hacerlo es usar un diccionario en lugar de una lista, pero luego, en Python, las clases son solo azúcar de sintaxis para el diccionario, así que ¿por qué no usar clases? También obtienes validación de nombre gratuita porque las chicas de Python no pueden contener espacios o caracteres especiales que una cadena podría.
Lie Ryan
6

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__=Xes 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 especial defy classdeclaraciones normales y en el cuerpo de definición de clase.

user412090
fuente
5

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:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

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.

Tríptico
fuente
2
Puede generar tipos dinámicamente sin utilizar metaclases. >>> ayuda (tipo)
Ali Afshar
8
Incluso si no es consciente de ello, entonces está usando metaclases. type es una metaclase, de hecho, la más común. :-)
Veky
5

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

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

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.

David Raznick
fuente
5

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.

Charlie martin
fuente
1
Tampoco necesita objetos para realizar programación orientada a objetos; podría hacerlo con funciones de primera clase. Entonces no necesitas usar objetos. Pero están ahí por conveniencia. Así que no estoy seguro de qué punto estás tratando de hacer en el primer párrafo.
Tyler Crompton
1
Vuelve a mirar la pregunta.
Charlie Martin
4

Algunas bibliotecas GUI tienen problemas cuando varios subprocesos intentan interactuar con ellos. tkinteres 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 threadboxinteresante 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?

Noctis Skytower
fuente
4

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)

Mike A
fuente
7
No estoy seguro de que la motivación fuera la misma en Django.
Ali Afshar
3

¡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 RegexLexerque 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.

Benjamin Peterson
fuente
Bueno, en mi opinión, el caso de Pygments es simplemente innecesario. ¿Por qué no tener una colección simple como un dictado, por qué obligar a una clase a hacer esto?
Ali Afshar
4
Porque una clase agradable encapsula la idea de Lexer y tiene otros métodos útiles como guess_filename (), etc.
Benjamin Peterson
3

La forma en que usé las metaclases fue proporcionar algunos atributos a las clases. Toma por ejemplo:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

pondrá el atributo de nombre en cada clase que tendrá la metaclase establecida para apuntar a NameClass.

hiperbóreo
fuente
5
Si, esto funciona. También podría usar una superclase, que es al menos explícita y se puede seguir en el código. Por interés, ¿para qué usaste esto?
Ali Afshar
2

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 __superatributo para cada clase e instancia.

Eli Collins
fuente
1

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.p012017sintaxis, 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é SQLAlchemy DeclarativeMetapara iterar a través de un rango de las columnas y crea automáticamente campos modelo correspondientes a las columnas:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

Luego podría usar esta metaclase para la definición de mi modelo y acceder a los campos enumerados automáticamente en el modelo:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...
Geoffrey Hing
fuente
1

Parece que aquí se describe un uso legítimo : reescribir cadenas de documentos de Python con una metaclase.

mistermarko
fuente
0

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.

GeeF
fuente
0

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:

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls

        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • todo es objeto, entonces la clase es objeto
  • el objeto de clase es creado por metaclase
  • toda clase heredada del tipo es metaclase
  • la metaclase podría controlar la creación de clases
  • metaclase también podría controlar la creación de metaclase (por lo que podría repetirse para siempre)
  • esto es metaprogramación ... podría controlar el sistema de tipos en tiempo de ejecución
  • de nuevo, todo es objeto, este es un sistema uniforme, escriba crear tipo y escriba crear instancia
tonto
fuente
0

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.

mantener viva
fuente