python - real - 1 clase hereda 2 metaclases diferentes(abcmeta y meta definida por el usuario)
python meta class (4)
Tengo una clase 1 que necesita heredar de 2 metaclases diferentes que es Meta1 y abc.ABCMeta
Implementación actual:
Implementación de Meta1:
class Meta1(type):
def __new__(cls, classname, parent, attr):
new_class = type.__new__(cls, classname, parent, attr)
return super(Meta1, cls).__new__(cls, classname, parent, attr)
implementación de class1Abstract
class class1Abstract(object):
__metaclass__ = Meta1
__metaclass__ = abc.ABCMeta
implementación de mainclass
class mainClass(class1Abstract):
# do abstract method stuff
Sé que esto es incorrecto para implementar 2 meta diferentes dos veces.
Cambio la forma en que se carga metclass (algunos intentos) y obtengo este error TypeTrror: al llamar a las bases de metaclase
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Me quedé sin ideas...
EDITED 1
Probé esta solución, funciona, pero mainClass no es una instancia de class1Abstract.
print issubclass(mainClass, class1Abstract) # true
print isinstance(mainClass, class1Abstract) # false
Implementación de class1Abstract
class TestMeta(Meta1):
pass
class AbcMeta(object):
__metaclass__ = abc.ABCMeta
pass
class CombineMeta(AbcMeta, TestMeta):
pass
class class1Abstract(object):
__metaclass__ = CombineMeta
@abc.abstractmethod
def do_shared_stuff(self):
pass
@abc.abstractmethod
def test_method(self):
'''''' test method ''''''
Implementación de mainClass
class mainClass(class1Abstract):
def do_shared_stuff(self):
print issubclass(mainClass, class1Abstract) # True
print isinstance(mainClass, class1Abstract) # False
Como mainClass hereda de una clase abstracta, python debería quejarse de que test_method no se está implementando en mainClass. Pero no se queja de nada porque print isinstance (mainClass, class1Abstract) # False
dir(mainClass)
no tiene
[''__abstractmethods__'', ''_abc_cache'', ''_abc_negative_cache'', ''_abc_negative_cache_version'', ''_abc_registry'']
¡AYUDA!
EDITED 2
Implementación de class1Abstract
CombineMeta = type("CombineMeta", (abc.ABCMeta, Meta1), {})
class class1Abstract(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def do_shared_stuff(self):
pass
@abc.abstractmethod
def test_method(self):
'''''' test method ''''''
Implementación de mainClass
class mainClass(class1Abstract):
__metaclass__ = CombineMeta
def do_shared_stuff(self):
print issubclass(mainClass, class1Abstract) # True
print isinstance(mainClass, class1Abstract) # False
dir(mainClass)
ahora tiene los métodos mágicos de abstractmethod
[''__abstractmethods__'', ''__class__'', ''__delattr__'', ''__dict__'', ''__doc__'', ''__format__'', ''__getattribute__'', ''__hash__'', ''__init__'', ''__metaclass__'', ''__module__'', ''__new__'', ''__reduce__'', ''__reduce_ex__'', ''__repr__'', ''__setattr__'', ''__sizeof__'', ''__str__'', ''__subclasshook__'', ''__weakref__'', ''_abc_cache'', ''_abc_negative_cache'', ''_abc_negative_cache_version'', ''_abc_registry'', ''do_shared_stuff'', ''test_method'']
Pero Python no advierte sobre el método de prueba que no se está creando.
¡AYUDA!
En Python, cada clase solo puede tener una metaclase, no muchas. Sin embargo, es posible lograr un comportamiento similar (como si tuviera múltiples metaclases) mezclando lo que hacen estas metaclases.
Vamos a empezar simple. Nuestra propia metaclase, simplemente agrega un nuevo atributo a una clase:
class SampleMetaClass(type):
"""Sample metaclass: adds `sample` attribute to the class"""
def __new__(cls, clsname, bases, dct):
dct[''sample''] = ''this a sample class attribute''
return super(SampleMetaClass, cls).__new__(cls, clsname, bases, dct)
class MyClass(object):
__metaclass__ = SampleMetaClass
print("SampleMetaClass was mixed in!" if ''sample'' in MyClass.__dict__ else "We''ve had a problem here")
Esto imprime "¡SampleMetaClass se mezcló en!", Así que sabemos que nuestra metaclase básica funciona bien.
Ahora, en el otro lado, queremos una clase abstracta, en su forma más simple sería:
from abc import ABCMeta, abstractmethod
class AbstractClass(object):
__metaclass__ = ABCMeta
@abstractmethod
def implement_me(self):
pass
class IncompleteImplementor(AbstractClass):
pass
class MainClass(AbstractClass):
def implement_me(self):
return "correct implementation in `MainClass`"
try:
IncompleteImplementor()
except TypeError as terr:
print("missing implementation in `IncompleteImplementor`")
MainClass().implement_me()
Esto imprime la "implementación faltante en IncompleteImplementor
" seguido por "la implementación correcta en MainClass
". Entonces, la clase abstracta también funciona bien.
Ahora, tenemos 2 implementaciones simples y debemos combinar el comportamiento de las dos metaclases. Hay múltiples opciones aquí.
Opción 1 - subclasificación
¡Uno puede implementar un SampleMetaClass
como una subclase de ABCMeta
- las metaclases también son clases y uno puede incorporarlas!
class SampleMetaABC(ABCMeta):
"""Same as SampleMetaClass, but also inherits ABCMeta behaviour"""
def __new__(cls, clsname, bases, dct):
dct[''sample''] = ''this a sample class attribute''
return super(SampleMetaABC, cls).__new__(cls, clsname, bases, dct)
Ahora, cambiamos la metaclase en la definición de AbstractClass
:
class AbstractClass(object):
__metaclass__ = SampleMetaABC
@abstractmethod
def implement_me(self):
pass
# IncompleteImplementor and MainClass implementation is the same, but make sure to redeclare them if you use same interpreter from the previous test
Y vuelve a ejecutar nuestras dos pruebas:
try:
IncompleteImplementor()
except TypeError as terr:
print("missing implementation in `IncompleteImplementor`")
MainClass().implement_me()
print("sample was added!" if ''sample'' in IncompleteImplementor.__dict__ else "We''ve had a problem here")
print("sample was added!" if ''sample'' in MainClass.__dict__ else "We''ve had a problem here")
Esto aún imprimirá que IncompleteImplementor
no se implementó correctamente, que MainClass
está, y que ambos ahora tienen agregado el atributo de nivel de clase de sample
. Lo que hay que tener en cuenta aquí es que la parte de Sample
de la metaclase se aplicó con éxito también a IncompleteImplementor
(bueno, no hay razón para que no lo hiciera).
Como es de esperarse, la isinstance
y la issubclass
siguen funcionando como se supone que deben:
print(issubclass(MainClass, AbstractClass)) # True, inheriting from AbtractClass
print(isinstance(MainClass, AbstractClass)) # False as expected - AbstractClass is a base class, not a metaclass
print(isinstance(MainClass(), AbstractClass)) # True, now created an instance here
Opción 2 - componer metaclases
De hecho, hay una opción en la pregunta en sí, solo se requiere una pequeña solución. Declare la nueva metaclase como una composición de varias metaclases más simples para mezclar su comportamiento:
SampleMetaWithAbcMixin = type(''SampleMetaWithAbcMixin'', (ABCMeta, SampleMetaClass), {})
Como anteriormente, cambie la metaclase para una MainClass
AbstractClass
(y nuevamente, IncompleteImplementor
y MainClass
no cambian, pero MainClass
si están en el mismo intérprete):
class AbstractClass(object):
__metaclass__ = SampleMetaWithAbcMixin
@abstractmethod
def implement_me(self):
pass
A partir de aquí, la ejecución de las mismas pruebas debería producir los mismos resultados: ABCMeta
aún funciona y garantiza que se implementa @abstractmethod
-s, SampleMetaClass
agrega un atributo de sample
.
Personalmente prefiero esta última opción, por la misma razón que generalmente prefiero la composición a la herencia: cuantas más combinaciones eventualmente se necesiten entre múltiples clases (meta), más simple sería con la composición.
Más sobre metaclases
Finalmente, la mejor explicación de las metaclases que he leído es la siguiente respuesta: ¿Qué es una metaclase en Python?
En su código EDITADO (tanto el 1 como el 2), casi ha terminado. Lo único incorrecto es cómo usas tu isinstance
. Desea comprobar si una instancia de clase (en este caso, self
) es una instancia de una clase determinada ( class1Abstract
). Por ejemplo:
class mainClass(class1Abstract):
def do_shared_stuff(self):
print issubclass(mainClass, class1Abstract) # True
print isinstance(self, class1Abstract) # True
No es necesario configurar dos metaclases: Meta1
debe heredar de abc.ABCMeta
.
Por defecto, Python solo se queja de que la clase tiene métodos abstractos cuando intenta crear una instancia de la clase, no cuando crea la clase. Esto se debe a que la metaclase de la clase sigue siendo ABCMeta
(o subtipo de la misma), por lo que está permitido tener métodos abstractos.
Para obtener lo que desea, necesita escribir su propia metaclase que genera un error cuando detecta que __abstractmethods__
no está vacío. De esta manera, debe indicar explícitamente cuándo una clase ya no tiene permitido métodos abstractos.
from abc import ABCMeta, abstractmethod
class YourMeta(type):
def __init__(self, *args, **kwargs):
super(YourMeta, self).__init__(*args, **kwargs)
print "YourMeta.__init__"
def __new__(cls, *args, **kwargs):
newcls = super(YourMeta, cls).__new__(cls, *args, **kwargs)
print "YourMeta.__new__"
return newcls
class ConcreteClassMeta(ABCMeta):
def __init__(self, *args, **kwargs):
super(ConcreteClassMeta, self).__init__(*args, **kwargs)
if self.__abstractmethods__:
raise TypeError("{} has not implemented abstract methods {}".format(
self.__name__, ", ".join(self.__abstractmethods__)))
class CombinedMeta(ConcreteClassMeta, YourMeta):
pass
class AbstractBase(object):
__metaclass__ = ABCMeta
@abstractmethod
def f(self):
raise NotImplemented
try:
class ConcreteClass(AbstractBase):
__metaclass__ = CombinedMeta
except TypeError as e:
print "Couldn''t create class --", e
class ConcreteClass(AbstractBase):
__metaclass__ = CombinedMeta
def f(self):
print "ConcreteClass.f"
assert hasattr(ConcreteClass, "__abstractmethods__")
c = ConcreteClass()
c.f()
Qué salidas:
YourMeta.__new__
YourMeta.__init__
Couldn''t create class -- ConcreteClass has not implemented abstract methods f
YourMeta.__new__
YourMeta.__init__
ConcreteClass.f