Search code examples
pythonclassinheritancesuperclass

Create instances of identical class but with different superclasses?


I am new to OO-progamming with Python and most likely the answer to my question is out there. But I do not know what to search for. So please be forbearing and I hope my MWE is clear.

I have a class TA which I want to use for two different use-cases:

  1. within a standalone script
  2. within a GUI

The difference between both use cases are minor and can be handled with conditions inside the class initialization and its methods. However, for the functionality of the class it is of importance, that in

  1. the class is defined as TA(object)
  2. the class is defined as TA(QObject)

I read a lot about superclasses and __new__ vs. __init__ but I could not find a working, yet simple solution.

Background

Why do I want to do this? In case I use the class within the GUI all the stdout is redirected to a QTextEdit widget, in case it is called from the script, the stdout goes to the terminal. The GUI case only works with QObject and I am glad that I got that working. However, I run into problems when using the class without the GUI but defined as QObject.

Example

Baseclass TA.py:

from PyQt5.QtCore import QObject

class TA(object):  # Option 1
# class TA(QObject): # Option 2
    def __init__(self, fruits):
        if isinstance(self,QObject):
            super().__init__()  # required in case of QObject
        
        self.init(fruits)

    def init(self, fruits):
        print('Give me some ' + fruits)

Calling Script TAscript.py:

from TA import *

TA('oranges')  # <<<< should be created as object

Calling GUI TAgui.py:

import sys
from PyQt5.QtWidgets import (QApplication,QMainWindow)
from TA import *

# GUI class
class TAgui(QMainWindow):
 
    def __init__(self):
        super().__init__()
        self.createInstanceOfTA()

    def createInstanceOfTA(self):
        TA('apples')  # <<<< should be created as QObject

# create GUI etc.
def main():
    qapp = QApplication(sys.argv)
    TAgui()

if __name__ == '__main__':
    main()

Can you guide me on how to achieve what I want without two basically identical classes?


Solution

  • The issue here is that you're trying to have a single class that behaves as two different types of classes depending on the context. This is tricky because in Python a class's type (and its parent type) are determined when the class is defined, not when an instance of the class is created.

    There are a few ways to solve this problem, but they all involve some trade-off in terms of complexity or elegance.

    Create separate classes for the two use cases. This is the most straightforward solution. You would define two classes, TAObject and TAQObject, that both implement the init method. TAObject would inherit from object and TAQObject would inherit from QObject. This might seem like a lot of code duplication, but it can be minimized by having both classes delegate to a shared helper function or class for the common functionality.

    Use a factory function or class. Instead of directly instantiating TA, you would call a function or create an instance of a factory class that returns an instance of the correct class based on the context. The factory could determine the context based on an argument that you pass in, or it could infer it from some global state (like whether QApplication.instance() returns None).

    Change the class of an instance after it's created. Python does allow changing the class of an instance after it's created, by assigning to its __class__ attribute. You could define TA to initially be an instance of object, and then change its class to QObject if it's being used in a GUI context. However, this is a rather advanced feature and can easily lead to confusing code, so I would only recommend it if you're comfortable with the idea and the other options aren't suitable.

    Here's an example of the factory function approach:

    from PyQt5.QtCore import QObject
    
    class TA(object):  
        def __init__(self, fruits):
            self.init(fruits)
    
        def init(self, fruits):
            print('Give me some ' + fruits)
    
    class TAQObject(TA, QObject):
        def __init__(self, fruits):
            QObject.__init__(self)
            super().__init__(fruits)
    
    def create_TA(fruits, use_qobject=False):
        if use_qobject:
            return TAQObject(fruits)
        else:
            return TA(fruits)
    

    And then in your scripts, you would use it like this:

    # TAscript.py
    from TA import create_TA
    create_TA('oranges')
    

    # TAgui.py
    from TA import create_TA
    class TAgui(QMainWindow):
        def __init__(self):
            super().__init__()
            self.createInstanceOfTA()
    
        def createInstanceOfTA(self):
            create_TA('apples', use_qobject=True)
    
    def main():
        qapp = QApplication(sys.argv)
        TAgui()
    
    if __name__ == '__main__':
        main()
    

    This approach keeps the common functionality in a single place (TA), lets TAQObject inherit from both TA and QObject to combine their functionality, and uses create_TA as a way to decide which class to use based on the context. Note that you can further enhance the create_TA function to determine the use_qobject flag based on your application's state, so that you don't need to specify it manually.