Flame changes how multiple inheritance / super works in python

Stumbled upon this really bizarre issue in flame while developing, wondered if anyone had any insights into why this is happening? If not, at the very least I hope this post will be useful to any other developers confused as to why their code is not working.

To begin, I’m running flame 2025, which uses python 3.11.5, but have tested on flame 2026 as well and got the same results.

Basically, the way multiple inheritance and super works in python is that the super call will try to find the first instance of that method in parent classes as defined by the method resolution order.
So if I start with a simple example;


class A:    
    def __init__(self, a=None):
        print("Enter A")
        self.a = a
        print("Exit A")

class B:
    def __init__(self, b=None):
        print("Enter B")
        self.b = b
        print("Exit B")

class C(A, B):
    def __init__(self):
        """Initialize the class."""
        print("Enter C")
        super().__init__()
        print("Exit C")

We see that class C has as super call to __init__ which we would expect to resolve to class A’s init method. This is true when all the classes are in the same module, and we get the expected output - from my python console in flame;

[07/09/2025 10:48:16 AM]
>>> import sys
>>> sys.path.insert(0, '/users/edwardspencer/pipeline/experiments/super-mro')
>>> from single import C
[07/09/2025 10:49:08 AM]
>>> c = C()
Enter C
Enter A
Exit A
Exit C

So far, so good. Now if we break up our classes into separate modules (as is normal for large software projects) so that we have the following structure;

.
├── multifile
│   ├── a.py
│   ├── b.py
│   └── c.py
└── single.py

Then we try the same thing as above (before you ask, this is a brand new session of flame);

[07/09/2025 10:50:23 AM]
>>> import sys
>>> sys.path.insert(0, '/users/edwardspencer/pipeline/experiments/super-mro')
>>> from multifile.c import C
[07/09/2025 10:50:25 AM]
>>> c = C()
Enter C
Enter A
Enter B
Exit B
Exit A
Exit C

As you can see, C init is called, it enters A first and then it enters B.
This might not seem like a big deal but 1) if the method signatures of either of the base classes differ you’ll get error and 2) you’re unintentionally running code which could have unexpected consequences.

I should also further note that outside of flame, e.g. running this code in regular python you only get the first behaviour. It is common practice in DCC development to test your GUIs outside of the DCC because it’s significantly faster not waiting for the application to load before running the small bit you need to test - however with flame changing how python works at such a fundamental level it’s pretty hard to have confidence in that testing…

Just guessing but this is probably a side effect that Flame will load the python files (in the various python shared folders) to search for Hooks when it launches.

I have my scripts split up into several files, but one issue is that if I make changes in an IDE and run the “Rescan Python Scripts” (sorry this is from memory) in Flame, the changes might not show up. This is because Flame is just scanning for hooks when it reloads the python scripts. I would have to restart Flame to get the updates to work. BUT, i’ve noticed that if the code changes are inside a hook then I “Rescan..” works and I can see the changes.

This is one reason why you see the “monolithic” style of Flame scripts where everything is put into one file. It also makes sense for 3rd party scripts because this makes versioning and deployment a bit easier…dealing with one file is better than distributing dozens.

For my pipeline scripts in general, I started with breaking out python modules to be able to reuse utility functions across different scripts. For example, I have a utility script for Shotgun functions. But now, I’ve been leaning toward the ‘monolithic’ style of scripting and stuff a lot of utility functions into one file even if it means duplicating code (I make them private functions). The main reason is that scripting for pipeline is very brittle and if you have a bunch of dependencies and make a change then you can break a lot of stuff. Also, say you needed to roll back only one script in mid production. I can just revert one file and then we’re back up instead of trying to figure out which version of the Shotgun utilities I was using for an old version of a main script.

I don’t think it’s realistic to expect python environments to work perfectly outside the DCC. Even Nuke api (which is pretty amazing and comprehensive) has it’s quirks and bugs and I have to test things out inside Nuke to verify behavior.

You have to strike a balance between designing something elegant or just trying to make something work.

2 Likes

I know you will not like this advice, but multiple inheritance is tricky even if a language provides it

[pman@juliet inheritance_problem]$ tree
.
├── multifile
│   ├── a.py
│   ├── b.py
│   └── c.py
└── single.py
[pman@juliet inheritance_problem]$ cat single.py

class A:    
    def __init__(self, a=None):
        print("Enter A")
        self.a = a
        print("Exit A")


class B:
    def __init__(self, b=None):
        print("Enter B")
        self.b = b
        print("Exit B")


class C(A, B):
    def __init__(self):
        """Initialize the class."""
        print("Enter C")
        super().__init__()
        print("Exit C")
[pman@juliet inheritance_problem]$
/opt/Autodesk/python/$VERSION/bin/python3

Python 3.11.5 (main, Sep 21 2023, 10:22:49) 
[GCC 11.2.1 20220127 (Red Hat 11.2.1-9)] on linux

Type "help", "copyright", "credits" or "license"
for more information.

>>> import sys
>>> sys.path.insert(0, '~/workspace/inheritance_problem')
>>> from single import C
>>> c = C()
Enter C
Enter A
Exit A
Exit C
>>> quit()
[pman@juliet inheritance_problem]$ cat ./multifile/a.py

class A:    
    def __init__(self, a=None):
        print("Enter A")
        self.a = a
        print("Exit A")
[pman@juliet inheritance_problem]$ cat ./multifile/b.py

class B:
    def __init__(self, b=None):
        print("Enter B")
        self.b = b
        print("Exit B")
[pman@juliet inheritance_problem]$ cat ./multifile/c.py

class C(A, B):
    def __init__(self):
        print("Enter C")
        super().__init__()
        print("Exit C")
[pman@juliet inheritance_problem]$
/opt/Autodesk/python/$VERSION/bin/python3

Python 3.11.5 (main, Sep 21 2023, 10:22:49) 
[GCC 11.2.1 20220127 (Red Hat 11.2.1-9)] on linux

Type "help", "copyright", "credits" or "license" 
for more information.

>>> import sys
>>> sys.path.insert(0, '~workspace/inheritance_problem')
>>> from multifile.c import C
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "~/workspace/inheritance_problem/multifile/c.py", line 1, in <module>
    class C(A, B):
            ^
NameError: name 'A' is not defined
sed -i '1i from multifile.a import A' multifile/c.py
sed -i '2i from multifile.b import B' multifile/c.py
sed -i '3i ' multifile/c.py
[pman@juliet inheritance_problem]$ cat ./multifile/c.py
from multifile.a import A
from multifile.b import B

class C(A, B):
    def __init__(self):
        print("Enter C")
        super().__init__()
        print("Exit C")
[pman@juliet inheritance_problem]$
/opt/Autodesk/python/$VERSION/bin/python3

Python 3.11.5 (main, Sep 21 2023, 10:22:49) 
[GCC 11.2.1 20220127 (Red Hat 11.2.1-9)] on linux

Type "help", "copyright", "credits" or "license" 
for more information.

>>> import sys
>>> sys.path.insert(0, '~workspace/inheritance_problem')
>>> from multifile.c import C
>>> c = C()
Enter C
Enter A
Exit A
Exit C
>>> quit()

As for why my code doesn’t work?
I have larger, unrelated problems, the biggest of which is I’m not and never have been a computer programmer.

It’s been a long time since I worked extensively with multiple inheritance, and in C++ no less. So I can’t comment on the specifics of your setup.

But just on coding principles regardless of language, a class that has two parent classes, should call the default constructor of each parent class. Otherwise a portion of your object never gets a chance to initialize correctly.

So I would consider your first result a bug and the multi-file result the correct one.

This is different for regular method calls and constructors. The logic to choose which parent class to resolve a method call to may well apply to regular methods, but shouldn’t for default constructores.