BUG: Fix broken multiple inheritance in ScriptedLoadableModule classes#6243
Conversation
Missing calls to `super().__init__()` in the `ScriptedLoadableModule` classes break multiple inheritance. The init method is skipped in any derived classes that come after the ScriptedLoadableModule class in the MRO. Adding `super().__init__()` calls to each of the ScriptedLoadableModule classes fixes this issue and correctly supports multiple inheritance. - ScriptedLoadableModule.__init__ - ScriptedLoadableModuleWidget.__init__ - ScriptedLoadableModuleLogic.__init__
|
Thanks for the detailed explanation 🙏 Following b2ba482 (ENH: Add support for building Python 3.6) removing support for python 2.7, only new style python classes are supported and using For references: |
Reading through that documentation more carefully, it seems it is possible to fix the behavior for
This indicates that class Mixin:
def __init__(self):
super().__init__()
print('Mixin')
class UnitTest:
def __init__(self):
# super call cannot be added.
print('UnitTest')
class ModuleTest(UnitTest):
def __init__(self):
# next class in MRO after UnitTest (Mixin.__init__)
super(UnitTest, self).__init__()
# immediate next class in MRO (UnitTest.__init__)
super(ModuleTest, self).__init__()
print('ModuleTest')
class MyModuleTest(ModuleTest, Mixin):
def __init__(self):
super().__init__()
print('MyModuleTest')
_ = MyModuleTest() # Mixin / UnitTest / ModuleTest / MyModuleTestThere is a risk in this implementation: if a super call is ever added to the I will add commits to this branch introducing the above change, along with a test to check for regression that ensures |
See Slicer#6243 (comment) for more information. `unittest.TestCase` does not include a `super().__init__()` call, so we must invoke both `TestCase.__init__` along with the `__init__` for any mixins of derived classes. `super(unittest.TestCase, self).__init__()` explicitly calls the `__init__` of the next class in the MRO, _after `unittest.TestCase`_; so adding this second `__init__` call solves the issue. Thus, this is now supported: ```python class MyModuleTest(ScriptedLoadableModuleTest, VTKObservationMixin): def runTest(self): self.addObserver(...) ```
…asses. See Slicer#6243 (comment) for more information. New tests in SlicerUtilVTKObservationMixinTests: Ensure VTKObservationMixin functions in combination with the ScriptedLoadableModule classes: - test_moduleMixin - test_moduleLogicMixin - test_moduleTestMixin Ensure VTKObservationMixin.__init__ called exactly in combination with ScriptedLoadableModuleTest: - test_moduleTestInitCount
See Slicer#6243 (comment) for more information. `unittest.TestCase` does not include a `super().__init__()` call, so we must invoke both `TestCase.__init__` along with the `__init__` for any mixins of derived classes. `super(unittest.TestCase, self).__init__()` explicitly calls the `__init__` of the next class in the MRO, _after `unittest.TestCase`_; so adding this second `__init__` call solves the issue. Thus, this is now supported: ```python class MyModuleTest(ScriptedLoadableModuleTest, VTKObservationMixin): def runTest(self): self.addObserver(...) ```
…asses. See Slicer#6243 (comment) for more information. New tests in SlicerUtilVTKObservationMixinTests: Ensure VTKObservationMixin functions in combination with the ScriptedLoadableModule classes: - test_moduleMixin - test_moduleLogicMixin - test_moduleTestMixin Ensure VTKObservationMixin.__init__ called exactly in combination with ScriptedLoadableModuleTest: - test_moduleTestInitCount
See Slicer#6243 (comment) for more information. `unittest.TestCase` does not include a `super().__init__()` call, so we must invoke both `TestCase.__init__` along with the `__init__` for any mixins of derived classes. `super(unittest.TestCase, self).__init__()` explicitly calls the `__init__` of the next class in the MRO, _after `unittest.TestCase`_; so adding this second `__init__` call solves the issue. Thus, this is now supported: ```python class MyModuleTest(ScriptedLoadableModuleTest, VTKObservationMixin): def runTest(self): self.addObserver(...) ```
…asses. See Slicer#6243 (comment) for more information. New tests in SlicerUtilVTKObservationMixinTests: Ensure VTKObservationMixin functions in combination with the ScriptedLoadableModule classes: - test_moduleMixin - test_moduleLogicMixin - test_moduleTestMixin Ensure VTKObservationMixin.__init__ called exactly in combination with ScriptedLoadableModuleTest: - test_moduleTestInitCount
…asses. See Slicer#6243 (comment) for more information. New tests in SlicerUtilVTKObservationMixinTests: Ensure VTKObservationMixin functions in combination with the ScriptedLoadableModule classes: - test_moduleWidgetMixin - test_moduleLogicMixin - test_moduleTestMixin Ensure VTKObservationMixin.__init__ called exactly once in combination with ScriptedLoadableModuleTest: - test_moduleTestInitCount
See #6243 (comment) for more information. `unittest.TestCase` does not include a `super().__init__()` call, so we must invoke both `TestCase.__init__` along with the `__init__` for any mixins of derived classes. `super(unittest.TestCase, self).__init__()` explicitly calls the `__init__` of the next class in the MRO, _after `unittest.TestCase`_; so adding this second `__init__` call solves the issue. Thus, this is now supported: ```python class MyModuleTest(ScriptedLoadableModuleTest, VTKObservationMixin): def runTest(self): self.addObserver(...) ```
…asses. See #6243 (comment) for more information. New tests in SlicerUtilVTKObservationMixinTests: Ensure VTKObservationMixin functions in combination with the ScriptedLoadableModule classes: - test_moduleWidgetMixin - test_moduleLogicMixin - test_moduleTestMixin Ensure VTKObservationMixin.__init__ called exactly once in combination with ScriptedLoadableModuleTest: - test_moduleTestInitCount
See Slicer#6243 (comment) for more information. `unittest.TestCase` does not include a `super().__init__()` call, so we must invoke both `TestCase.__init__` along with the `__init__` for any mixins of derived classes. `super(unittest.TestCase, self).__init__()` explicitly calls the `__init__` of the next class in the MRO, _after `unittest.TestCase`_; so adding this second `__init__` call solves the issue. Thus, this is now supported: ```python class MyModuleTest(ScriptedLoadableModuleTest, VTKObservationMixin): def runTest(self): self.addObserver(...) ```
…asses. See Slicer#6243 (comment) for more information. New tests in SlicerUtilVTKObservationMixinTests: Ensure VTKObservationMixin functions in combination with the ScriptedLoadableModule classes: - test_moduleWidgetMixin - test_moduleLogicMixin - test_moduleTestMixin Ensure VTKObservationMixin.__init__ called exactly once in combination with ScriptedLoadableModuleTest: - test_moduleTestInitCount
(cherry picked from commit 7cfa0fc) See Slicer#6243 (comment) for more information. `unittest.TestCase` does not include a `super().__init__()` call, so we must invoke both `TestCase.__init__` along with the `__init__` for any mixins of derived classes. `super(unittest.TestCase, self).__init__()` explicitly calls the `__init__` of the next class in the MRO, _after `unittest.TestCase`_; so adding this second `__init__` call solves the issue. Thus, this is now supported: ```python class MyModuleTest(ScriptedLoadableModuleTest, VTKObservationMixin): def runTest(self): self.addObserver(...) ```
…asses. (cherry picked from commit c59fd74) See Slicer#6243 (comment) for more information. New tests in SlicerUtilVTKObservationMixinTests: Ensure VTKObservationMixin functions in combination with the ScriptedLoadableModule classes: - test_moduleWidgetMixin - test_moduleLogicMixin - test_moduleTestMixin Ensure VTKObservationMixin.__init__ called exactly once in combination with ScriptedLoadableModuleTest: - test_moduleTestInitCount
Missing calls to
super().__init__()in theScriptedLoadableModuleclasses break multiple inheritance. The init method is skipped in any derived classes that come after the ScriptedLoadableModule class in the MRO.Adding super calls to each of the ScriptedLoadableModule classes fixes this issue and correctly supports multiple inheritance.
ScriptedLoadableModule.__init__ScriptedLoadableModuleWidget.__init__ScriptedLoadableModuleLogic.__init__For example, this class fails to initialize VTKObservationMixin:
In this case, swapping the base class order avoids the error as
VTKObservationMixindoes include a super call, however that solution is not perfect.MyModuleLogic.__base__is notScriptedLoadableModuleLogicas it should be, and it is impossible to pass the optionalparentparameter toScriptedLoadableModuleLogic.__init__.This change should not have any backwards compatibility issues, as currently the only way to support mixins is to explicitly call the mixin initializer, in which case the behavior of
super()is ignored.ScriptedLoadableModuleTestcannot be fixed because its base class,unittest.TestCase, does not have a super call. For such cases it would still be necessary to explicitly call the mixin initializer.