2012-06-18 15 views
5

Siguiendo this answer parece que una clase clase metaclase puede ser cambiado después se ha definido la clase utilizando el siguiente *:El establecimiento de un metaclase usando un decorador

class MyMetaClass(type): 
    # Metaclass magic... 

class A(object): 
    pass 

A = MyMetaClass(A.__name__, A.__bases__, dict(A.__dict__)) 

Definir una función

def metaclass_wrapper(cls): 
    return MyMetaClass(cls.__name__, cls.__bases__, dict(cls.__dict__)) 

me permite aplicar un decorador de una definición de clase como tal,

@metaclass_wrapper 
class B(object): 
    pass 

vER ms que la magia de la metaclase se aplica a B, sin embargo, B no tiene ningún atributo __metaclass__. Es el método anterior de una manera sensata para aplicar metaclases a las definiciones de clase, a pesar de que estoy definiting y re-definiting una clase, o ¿sería mejor simplemente escribiendo

class B(object): 
    __metaclass__ = MyMetaClass 
    pass 

supongo que hay algunas diferencias entre los dos métodos.


* Nota, la respuesta original en la cuestión vinculada, MyMetaClass(A.__name__, A.__bases__, A.__dict__), devuelve un TypeError:

TypeError: type() argument 3 must be a dict, not dict_proxy

Parece que el atributo __dict__ de A (la definición de clase) tiene un tipo dict_proxy, mientras el tipo del atributo __dict__ de una instancia de A tiene un tipo dict. ¿Por qué es esto? ¿Es esto una diferencia de Python 2.x vs. 3.x?

Respuesta

1

La clase no tiene ningún atributo __metaclass__ establecido ... ¡porque nunca lo configuró!

Qué metaclase usar normalmente se determina por un nombre __metaclass__ en un bloque de clase. El atributo __metaclass__ no está establecido por la metaclase. Por lo tanto, si invoca una metaclase directamente en lugar de establecer __metaclass__ y deja que Python la resuelva, no se establece el atributo __metaclass__.

De hecho, las clases normales son todas las instancias de la metaclase type, por lo que si la metaclase siempre se establece el atributo __metaclass__ en sus instancias a todas las clases tendría un atributo __metaclass__ (la mayoría de ellos establece en type).


No utilizaría su enfoque de decorador. Oculta el hecho de que una metaclase está involucrada (y cuál), sigue siendo una línea de repetición, y es complicado crear una clase a partir de las 3 características definitorias de (name, bases, attributes) solo para sacar esos 3 bits de la clase resultante, tirar la clase, y hacer una nueva clase de esos mismos 3 bits!

Al hacer esto en Python 2.x:

class A(object): 
    __metaclass__ = MyMeta 
    def __init__(self): 
     pass 

se obtendría aproximadamente el mismo resultado si hubieras escrito esto:

attrs = {} 
attrs['__metaclass__'] = MyMeta 
def __init__(self): 
    pass 
attrs['__init__'] = __init__ 
A = attrs.get('__metaclass__', type)('A', (object,), attrs) 

En realidad el cálculo de la metaclase es más complicado, ya que realmente tiene que haber una búsqueda a través de todas las bases para determinar si hay un conflicto de metaclase, y si una de las bases no tiene type como su metaclase y attrs no contiene __metaclass__, entonces la metaclase predeterminada es la metaclase del antepasado en lugar de type. Esta es una situación en la que espero que su "solución" de decorador difiera de usar __metaclass__ directamente. No estoy seguro de qué pasaría si utilizara su decorador en una situación en la que usar __metaclass__ le daría un error de conflicto de metaclase, pero no esperaría que fuera agradable.

Además, si hay otras metaclases involucradas, su método daría como resultado que se ejecuten primero (¡posiblemente modificando el nombre, las bases y los atributos) y luego los saca de la clase y los utiliza para crear un Nueva clase. Esto podría ser bastante diferente de lo que obtendría al usar __metaclass__.

En cuanto al __dict__ que no le da un diccionario real, eso es solo un detalle de implementación; Lo adivinaría por razones de rendimiento. Dudo que exista alguna especificación que diga que el __dict__ de una instancia (no de clase) tiene que ser del mismo tipo que el __dict__ de una clase (que también es una instancia por cierto, solo una instancia de una metaclase). El atributo __dict__ de una clase es un "dictproxy", que le permite buscar las teclas de los atributos como si fuera un dict pero aún no es un dict. type es quisquilloso sobre el tipo de su tercer argumento; quiere un dict real, no solo un objeto "dict-like" (vergüenza por estropear el tipado de patos). No es una cosa 2.x vs 3.x; Python 3 se comporta de la misma manera, aunque le ofrece una representación de cadena más agradable del dictproxy. Python 2.4 (que es el 2.x más antiguo que tengo disponible) también tiene objetos dictproxy para objetos de clase __dict__.

1

Mi resumen de su pregunta: "Probé una nueva y complicada manera de hacer algo, y no funcionó del todo. ¿Debo usar el modo simple en su lugar?"

Sí, debes hacerlo de la manera más sencilla. No ha dicho por qué está interesado en inventar una nueva forma de hacerlo.

+0

Me preguntaba si había un decorador equivalente a establecer el atributo '__metaclass__' en una definición de clase. Supongo que otra parte de mi pregunta es que mi "decorador" * pareció * funcionar, pero noté que la clase decorada no tiene el atributo '__metaclass__', por lo que claramente hay una diferencia entre los dos métodos; ¿Por qué es esto? – Chris

3

Es cierto que llegué un poco tarde a la fiesta. Sin embargo, me caí, valió la pena agregar esto.

Esto es completamente factible. Dicho esto, hay muchas otras formas de lograr el mismo objetivo. Sin embargo, la solución de decoración, en particular, permite una evaluación diferida (obj = dec(obj)), que no es válida con __metaclass__ dentro de la clase. En el estilo típico de decorador, mi solución está a continuación.

Hay una cosa complicada que puede encontrar si acaba de construir la clase sin cambiar el diccionario o copiar sus atributos. Cualquier atributo que la clase tenía previamente (antes de decorar) parecerá que falta. Por lo tanto, es absolutamente esencial copiarlos y ajustarlos como lo hice en mi solución.

Personalmente, me gusta ser capaz de hacer un seguimiento de cómo se envolvió un objeto. Entonces, agregué el atributo __wrapped__, que no es estrictamente necesario. También lo hace más como functools.wraps en Python 3 para las clases. Sin embargo, puede ser útil con la introspección. Además, se agrega __metaclass__ para actuar más como el caso de uso de la metaclase normal.

def metaclass(meta): 
    def metaclass_wrapper(cls): 
     __name = str(cls.__name__) 
     __bases = tuple(cls.__bases__) 
     __dict = dict(cls.__dict__) 

     for each_slot in __dict.get("__slots__", tuple()): 
      __dict.pop(each_slot, None) 

     __dict["__metaclass__"] = meta 

     __dict["__wrapped__"] = cls 

     return(meta(__name, __bases, __dict)) 
    return(metaclass_wrapper) 

Para un ejemplo trivial, tome lo siguiente.

class MetaStaticVariablePassed(type): 
    def __new__(meta, name, bases, dct): 
     dct["passed"] = True 

     return(super(MetaStaticVariablePassed, meta).__new__(meta, name, bases, dct)) 

@metaclass(MetaStaticVariablePassed) 
class Test(object): 
    pass 

Esto produce el resultado agradable ...

|1> Test.passed 
|.> True 

Usando el decorador en el menos habitual, pero de manera idéntica ...

class Test(object): 
    pass 

Test = metaclass_wrapper(Test) 

... rendimientos, como se esperaba , el mismo buen resultado.

|1> Test.passed 
|.> True 
Cuestiones relacionadas