I have a class1 that needs to inherit from 2 different metaclasses which is Meta1 and abc.ABCMeta
Current implementation:
Implementation of 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)
implementation of class1Abstract
class class1Abstract(object):
__metaclass__ = Meta1
__metaclass__ = abc.ABCMeta
implementation of mainclass
class mainClass(class1Abstract):
# do abstract method stuff
I know this is wrong to implement 2 different meta twice.
I change the way metclass is loaded (a few tries) and I get this TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I ran out of ideas...
EDITED 1
I tried this solution it works but the mainClass is not an instance of class1Abstract
print issubclass(mainClass, class1Abstract) # true
print isinstance(mainClass, class1Abstract) # false
Implementation of 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 '''
Implementation of mainClass
class mainClass(class1Abstract):
def do_shared_stuff(self):
print issubclass(mainClass, class1Abstract) # True
print isinstance(mainClass, class1Abstract) # False
Since mainClass inherits from an abstract class python should complain about test_method not being implemented in mainClass. But it doesn't complain anything because print isinstance(mainClass, class1Abstract) # False
dir(mainClass)
doesn't have
['__abstractmethods__', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version', '_abc_registry']
HELP!
EDITED 2
Implementation of 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 '''
Implementation of mainClass
class mainClass(class1Abstract):
__metaclass__ = CombineMeta
def do_shared_stuff(self):
print issubclass(mainClass, class1Abstract) # True
print isinstance(mainClass, class1Abstract) # False
dir(mainClass)
now have abstractmethod's magic methods
['__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']
But python doesn't warn about test_method not being instantiated
HELP!
Every object and class in Python is either an instance of a class or an instance of a metaclass. Every class inherits from the built-in basic base class object , and every class is an instance of the metaclass type .
ABCMeta metaclass provides a method called register method that can be invoked by its instance. By using this register method, any abstract base class can become an ancestor of any arbitrary concrete class.
In object-oriented programming, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances. Not all object-oriented programming languages support metaclasses.
A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes.
In Python, every class can only have one metaclass, not many. However, it is possible to achieve similar behaviour (like if it would have multiple metaclasses) by mixing what these metaclasses do.
Let's start simple. Our own metaclass, simply adds new attribute to a class:
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")
This prints "SampleMetaClass was mixed in!", so we know our basic metaclass works fine.
Now, on the other side, we want an abstract class, in its simplest it would be:
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()
This prints "missing implementation in IncompleteImplementor
" followed up by "correct implementation in MainClass
". So, the abstract class also works fine.
Now, we have 2 simple implementations and we need to mix together the behaviour of the two metaclasses. There are multiple options here.
Option 1 - subclassing
One can implement a SampleMetaClass
as a subclass of ABCMeta
- metaclasses are also classes and one can inhereit them!
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)
Now, we change the metaclass in the AbstractClass
definition:
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
And run both our tests again:
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")
This would still print that IncompleteImplementor
is not correctly implemented, that MainClass
is, and that both now have the sample
class-level attribute added. Thing to note here that Sample
part of the metaclass was successfully applied to the IncompleteImplementor
as well (well, there is no reason it wouldn't).
As it would be expected, isinstance
and issubclass
still work as supposed to:
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
Option 2 - composing metaclasses
In fact, there is this option in the question itself, it only required a small fix. Declare new metaclass as a composition of several simpler metaclasses to mix their behaviour:
SampleMetaWithAbcMixin = type('SampleMetaWithAbcMixin', (ABCMeta, SampleMetaClass), {})
As previously, change the metaclass for an AbstractClass
(and again, IncompleteImplementor
and MainClass
don't change, but redeclare them if in same interpreter):
class AbstractClass(object):
__metaclass__ = SampleMetaWithAbcMixin
@abstractmethod
def implement_me(self):
pass
From here, running same tests should yield same results: ABCMeta
still works and ensures that @abstractmethod
-s are implemented, SampleMetaClass
adds a sample
attribute.
I personally prefer this latter option, for the same reason as I generally prefer composition to the inheritance: the more combinations one eventually needs between multiple (meta)classes - the simpler it would be with composition.
More on metaclasses
Finally, the best explanation of metaclasses I've ever read is this SO answer: What is a metaclass in Python?
By default python only complains that class has abstract methods when you attempt to instantiate the class, not when you create the class. This is because the class's metaclass is still ABCMeta
(or subtype thereof), so it's permitted to have abstract methods.
To get what you want you would need to write your own metaclass that raises an error when it spots that __abstractmethods__
isn't empty. This way you have to explicitly state when a class is no longer allowed abstract methods.
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()
Which outputs:
YourMeta.__new__
YourMeta.__init__
Couldn't create class -- ConcreteClass has not implemented abstract methods f
YourMeta.__new__
YourMeta.__init__
ConcreteClass.f
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With