ivywit
ivywit

Reputation: 483

Python metaclass conflict/Type error

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

...

Upvotes: 3

Views: 9726

Answers (2)

Blckknght
Blckknght

Reputation: 104682

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.

Upvotes: 3

mgilson
mgilson

Reputation: 309821

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), {})

Upvotes: 5

Related Questions