3

I'm debugging a python script (python isn't my go to language), and this is the first time I'm working with metaclass's in python. I'm getting a metaclass conflict error when I run the code as it is below.

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

In attempting to solve it I commented out the metaclass declaration in MySQLMSoftware, thinking that it's redundant as it inherits from a class with the same metaclass declaration, but this leads to:

TypeError: Error when calling the metaclass bases
    module.__init__() takes at most 2 arguments (3 given)

Any insight, guidance or direction would be greatly appreciated. I've been reading about python's metaclass implementation and the issue isn't popping out so far.

MSoftware.py

from abc import ABCMeta, abstractmethod

class MSoftware(object) :
    __metaclass__ = ABCMeta

    def __init__(self,name,spark=None,mysql=None):
        self.name = name
        ...

MySQLMSoftware.py

from mouse import Mouse, MSoftware
from abc import ABCMeta, abstractmethod

class MySQLMSoftware(MSoftware): # Traceback always goes here
    __metaclass__ = ABCMeta

    MAX_ROWS = 30000000

    def __init__(self,name,years,spark=None,mysql=None):
        MSoftware.__init__(self,name,spark,mysql)
        self.years = years
        ...

TTime.py

from mouse.Mouse import Mouse
from mouse.MySQLMSoftware import MySQLMSoftware

class TTime(MySQLMSoftware) :

    DATABASE = "Database"
    NAME = "table"
    YEARS = [2014,2016]

    def __init__(self,spark=None,mysql=None):
        MySQLMSoftware.__init__(self,TTime.NAME,TTime.YEARS,spark,mysql)
        ...

main.py

import sys
from mouse.Mouse import Mouse
from mouse.TTime import TTime

...
6
  • To me, it looks like MSoftware has a metaclass (which isn't ABCMeta) -- Is that true? If so, you might be able to try __metaclass__ = type('MySQLMSoftware', (type(MSoftware), ABCMeta), {}). I don't have any of these classes to play around with, so I can't guarantee that it'll work... Commented Jul 15, 2016 at 19:37
  • Also, what version of python are you using? Commented Jul 15, 2016 at 19:40
  • Python 2.7. @mgilson I believe MSoftware's metaclass is ABCMeta, however it is extending object this is the first time I've ever seen the __metaclass__ declaration. I'll give your suggestion a shot. Commented Jul 15, 2016 at 19:44
  • @Keozon -- It's definitely python2.x since __metaclass__ syntax changes with python3.x and there would be no error just setting a __metaclass__ attribute on a class in python3.x. Commented Jul 15, 2016 at 19:45
  • I'm not sure if it's relevant to the real issue or just an artifact of copying the code into the question, but you're showing an MSoftware class in an MSoftware.py file, but your other file (where the exception is happening) is inheriting from an MSoftware class defined in a mouse module, not an MSoftware module. Can you find and show mouse.py? Perhaps mouse.MSoftware is not the same as MSoftware.MSoftware. Commented Jul 16, 2016 at 2:55

2 Answers 2

5

The problem is that when selecting a metaclass, python picks the most inherited version. However, you have two conflicting metaclasses in play (ABCMeta and whatever MSoftware has).

I think that the python3.x docs are slightly more correct than the python2.x docs in this regard:

The appropriate metaclass for a class definition is determined as follows:

  • if no bases and no explicit metaclass are given, then type() is used
  • if an explicit metaclass is given and it is not an instance of type(), > then it is used directly as the metaclass
  • if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used

One solution is to create a new metaclass that is the mixture of the two:

class NewMeta(type(MSoftware), ABCMeta):
    pass

In this case, since all of the metaclasses are "instances" of MSoftware's metaclass, NewMeta will be selected because it is the most derived metaclass and things should work (provided that MSoftware's metaclass can be used in cooperative multi-inheritance).

Here's an example where I create the NewMeta dynamically to overcome this issue with some dummy classes:

class Meta1(type):
    pass

class Meta2(type):
    pass

class Class1(object):
    __metaclass__ = Meta1


# TypeError!
# class Class2(Class1):
#     __metaclass__ = Meta2

class Class2(Class1):
    __metaclass__ = type('Class2Meta', (type(Class1), Meta2), {})
Sign up to request clarification or add additional context in comments.

3 Comments

I appreciate your help with this. I've tested it locally and the issue doesn't seem to be occurring. Am I correct that there shouldn't be a conflict if MSoftware's metaclass is also ABCMeta? It might be an issue with the ipython notebook running on the server. Earlier I had an issue where it wasn't importing anything properly. I'll check with the server admin and see if they can fix it there. Either way thank you for your help!
@phelpsiv -- if MSoftware's metaclass is also ABCMeta then there should be no conflict (though you shouldn't need to supply a metaclass on your class either in that case). To introspect the metaclass for MSoftware, you can print type(MSoftware).
It took me 4 days to find this answer. You saved my day!
3

The issue has to do with your imports, and confusion between identically named classes and modules. The MSoftware you're importing in MySQLMSoftware.py is the module implemented in MSoftware.py, not the class of the same name within that module. To get the latter (without changing the imports), you'd need to use MSoftware.MSoftware. There may be a similar issue with the Mouse class and module (which is even worse, since the top level package is named mouse too).

Try changing the line:

from mouse import Mouse, MSoftware

To these two lines:

from mouse.Mouse import Mouse
from mouse.MSoftware import MSoftware

This will fix the metaclass conflict issue, and make the metaclass declaration in the MySQLMSoftware class unnecessary.

I'd like to note that the root cause of the issue (from a higher level perspective) is poor module design. You have several modules that each appear to contain a single class with the same name as them. That's a common project layout in other languages (like Java), but it's not necessary or desirable in Python. There's never a good reason for mouse.Mouse.Mouse to be a thing.

Probably you should combine most (or maybe all) of your package's modules into a smaller number of files, probably starting by putting most of the stuff in mouse/__init__.py (or a top level mouse.py if you combine them all and don't need the package any more). This way you won't have quite so many redundant imports.

2 Comments

This makes sense, given the error message module.__init__() takes at most 2 arguments (3 given)
This answer really helped me on my current implementation. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.