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:
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
TA(object)
TA(QObject)
I read a lot about superclasses and __new__
vs. __init__
but I could not find a working, yet simple solution.
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.
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?
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.