0

I'm having trouble with getting multiple dynamic inheritance to work. These examples make the most sense to me(here and here), but there's not enough code in one example for me to really understand what's going on and the other example doesn't seem to be working when I change it around for my needs (code below).

I'm creating a universal tool that works with multiple software packages. In one software, I need to inherit from 2 classes: 1 software specific API mixin, and 1 PySide class. In another software I only need to inherit from the 1 PySide class.

The least elegant solution that I can think of is to just create 2 separate classes (with all of the same methods) and call either one based on the software that's running. I have a feeling there's a better solution.

Here's what I'm working with:

## MainWindow.py

import os
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
    
# Build class
def build_main_window(*arg):
    
    class Build(arg):
        def __init__(self):
            super( Build, self ).__init__()

        # ----- a bunch of methods

# Get software
software = os.getenv('SOFTWARE')

# Run tool
if software == 'maya':
    build_main_window(maya_mixin_class, QtGui.QWidget)
if software == 'houdini':
    build_main_window(QtGui.QWidget)

I'm currently getting this error:

#     class Build(arg):
# TypeError: Error when calling the metaclass bases
#     tuple() takes at most 1 argument (3 given) # 

Thanks for any help!

EDIT:

## MainWindow.py

import os
    
# Build class 
class BuildMixin():
    def __init__(self):
        super( BuildMixin, self ).__init__()

    # ----- a bunch of methods

def build_main_window(*args):
    return type('Build', (BuildMixin, QtGui.QWidget) + args, {})

# Get software
software = os.getenv('SOFTWARE')

# Run tool
if software == 'maya':
    from maya.app.general.mayaMixin import MayaQWidgetDockableMixin

    Build = build_main_window(MayaQWidgetDockableMixin)

if software == 'houdini':
    Build = build_main_window()
Community
  • 1
  • 1
Mike Bourbeau
  • 481
  • 11
  • 29
  • 1
    That's because it's trying to subclass the tuple of `args` itself. If you want dynamic multiple inheritance, use `type(name, bases, dct)`. – jonrsharpe Oct 05 '16 at 16:10
  • To avoid the error you can add asterisk before *arg when you use it: class Build(*arg). Notice that the call to super inside the constructor might call different things depending on what you pass as arg. – jerry Oct 05 '16 at 17:08

2 Answers2

3

The error in your original code is caused by failing to use tuple expansion in the class definition. I would suggest simplifying your code to this:

# Get software
software = os.getenv('SOFTWARE')

BaseClasses = [QtGui.QWidget]
if software == 'maya':
    from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
    BaseClasses.insert(0, MayaQWidgetDockableMixin)

class Build(*BaseClasses):
    def __init__(self, parent=None):
        super(Build, self).__init__(parent)

UPDATE:

The above code will only work with Python 3, so it looks like a solution using type() will be needed for Python 2. From the other comments, it appears that the MayaQWidgetDockableMixin class may be a old-style class, so a solution like this may be necessary:

def BaseClass():
    bases = [QtGui.QWidget]
    if software == 'maya':
        from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
        class Mixin(MayaQWidgetDockableMixin, object): pass
        bases.insert(0, Mixin)
    return type('BuildBase', tuple(bases), {})

class Build(BaseClass()):
    def __init__(self, parent=None):
        super(Build, self).__init__(parent)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks for the reply, ekhumoro! I'm getting a syntax error at the asterisk at `class Build(*BaseClasses):`. I'm also getting an error in my IDE. Even directly copy and pasting your code causes errors to show up. I'm not familiar with this extension method, but it seems like the right one to go with if I can get it working. Any idea why I'm getting this error? – Mike Bourbeau Oct 05 '16 at 19:08
  • @MikeBourbeau. Damn - it looks like this works in Python 3, but not Python 2 (which I assume you must be using). – ekhumoro Oct 05 '16 at 19:12
  • Yep, I am :/ well I have to move to Python 3 at some point because of Maya 2017... maybe this is the day I start migrating – Mike Bourbeau Oct 05 '16 at 19:19
  • @MikeBourbeau. I've added an alternative solution that *should* work for Python 2 (I can't fully test it myself). – ekhumoro Oct 05 '16 at 19:36
  • That solution worked perfectly! Thank you very much :D – Mike Bourbeau Oct 05 '16 at 20:09
1

arg is a tuple, you can't use a tuple as a base class.

Use type() to create a new class instead; it takes a class name, a tuple of base classes (can be empty) and the class body (a dictionary).

I'd keep the methods for your class in a mix-in method:

class BuildMixin():
    def __init__(self):
        super(BuildMixin, self).__init__()

    # ----- a bunch of methods

def build_main_window(*arg):
    return type('Build', (BuildMixin, QtGui.QWidget) + args, {})

if software == 'maya':
    Build = build_main_window(maya_mixin_class)
if software == 'houdini':
    Build = build_main_window()

Here, args is used as an additional set of classes to inherit from. The BuildMixin class provides all the real methods, so the third argument to type() is left empty (the generated Build class has an empty class body).

Since QtGui.QWidget is common between the two classes, I just moved that into the type() call.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thank you for the quick reply. I've set up my script, but I'm getting this error now: `# return type('Build', (BuildMixin, QtGui.QWidget) + args, {})` `# TypeError: Invalid base class used in type Shiboken.ObjectType. PySide only support multiple inheritance from python new style class.` I'm not exactly sure how this class is 'old' style? See my post above for what code I'm using – Mike Bourbeau Oct 05 '16 at 18:24
  • 1
    @MikeBourbeau: sorry I was away; as ekhumoro pointed out, `MayaQWidgetDockableMixin` is an old-style class and you'd have to add `object` into the mix here. – Martijn Pieters Oct 05 '16 at 21:17