tutorial real python metaprogramming metaclass

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