¿Hay una manera de establecer metaclase después de la definición de la clase?

Para establecer la metaclase de una clase, usamos el atributo __metaclass__ . Las metaclases se utilizan en el momento en que se define la clase, por lo que establecerla explícitamente después de que la definición de la clase no tenga efecto.

Esto es lo que sucede cuando trato de establecer las metaclases explícitamente;

 >>> class MetaClass(type): def __new__(cls, name, bases, dct): dct["test_var"]=True return type.__new__(cls, name, bases, dct) def __init__(cls, name, bases, dct): super(MetaClass, cls).__init__(name, bases, dct) >>> class A: __metaclass__=MetaClass >>> A.test_var True >>> class B: pass >>> B.__metaclass__=MetaClass >>> B.test_var Traceback (most recent call last): File "", line 1, in  B.test_var AttributeError: class B has no attribute 'test_var' 

La mejor idea que se me ocurre es volver a definir toda la clase y agregar el atributo __metaclass__ dinámicamente de alguna manera. ¿O conoce una mejor manera de establecer metaclase después de la definición de la clase?

Puede cambiar la metaclase después de la creación de la clase de la misma manera que puede cambiar la clase de un objeto, sin embargo, tendrá muchos problemas. Para empezar, la metaclase inicial debe ser diferente del type , no se __init__ y __new__ de la nueva metaclase (aunque puede llamar manualmente a __init__ o a un método que realice el trabajo de __init__ ).

Posiblemente la forma menos problemática de cambiar la metaclase es volver a crear la clase desde cero:

 B = MetaClass(B.__name__, B.__bases__, B.__dict__) 

Pero si insiste en cambiar la metaclase dinámicamente, primero debe definir B con una metaclase personalizada temporal:

 class _TempMetaclass(type): pass class B: __metaclass__ = _TempMetaclass # or: type('temp', (type, ), {}) 

Entonces puedes definir la metaclase así:

 class MetaClass(type): def __init__(cls, *a, **kw): super(MetaClass, cls).__init__(*a, **kw) cls._actual_init(*a, **kw) def _actual_init(cls, *a, **kw): # actual initialization goes here 

Y luego hacer algo como:

 B.__class__ = MetaClass MetaClass._actual_init(B, B.__name__, B.__bases__, B.__dict__) 

También debe asegurarse de que toda la inicialización de la clase se realice _actual_init . También puede agregar un classmethod de classmethod a la metaclase que cambia la metaclase por usted.

Ambas soluciones tienen el pequeño inconveniente de que las bases de B serían limitadas: deben ser compatibles tanto con la metaclase original como con la nueva, pero supongo que no es un problema en su caso.

El propósito de las metaclases no es modificar el acceso a los atributos, sino personalizar la creación de clases. El atributo __metaclass__ define el invocable usado para construir un objeto de tipo a partir de una definición de clase, por defecto a type() . En consecuencia, este atributo solo se evalúa en la creación de la clase . Obviamente, no tiene ningún sentido cambiarlo en un momento posterior, porque no puede personalizar la creación de clases para las clases que ya se han creado. Consulte la Referencia del lenguaje de Python, 3.4.3 Personalización de la creación de clases para más detalles.

Las metaclases simplemente no son la herramienta adecuada para lo que obviamente quieres hacer. Si simplemente desea agregar un nuevo atributo a una clase ya existente, ¿por qué no lo asigna?

Puedes pensar que una metaclase describe lo que hace la statement de la class . Por eso es imposible configurar la metaclase más tarde: el código de las clases ya se ha convertido en un objeto de tipo, es demasiado tarde para cambiarlo.

Puede simplemente subclasificar su tipo original para agregar una metaclase:

 class C(B): __metaclass__=MetaClass 

¿Por qué necesitas esto? No creo que esto sea posible, pero si puede explicar el caso de uso, probablemente pueda sugerir una alternativa.

En la línea de redefinir a toda la clase, lo que podría hacer es esto:

 import types NewClass = types.ClassType('NewClass', (object, ), {'__metaclass__':MetaClass}) 

Luego simplemente copie todos los métodos anteriores:

 for item in dir(OldClass): setattr(NewClass, item, getattr(OldClass, item)) 

No puedo probar esto ahora mismo (no tengo acceso a Python en mi máquina en este momento :-(), pero a menos que la metaclase sea ​​imutable, quizás puedas probar algo como esto:

 >>> class B: pass >>> setattr(B, "__metaclass__", MetaClass) 

Nuevamente, no puedo probarlo ahora mismo, por lo que me disculpo si esta es una respuesta no válida, solo trato de ayudar. 🙂