Python Forum
[PyQt] Seeking guidance on PyQt Instantiation and correct way to use an instance of QWidget
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyQt] Seeking guidance on PyQt Instantiation and correct way to use an instance of QWidget
#1
Hey everyone,

I have a few questions (sorry), please see the example code to understand what the questions are referring to. I also recommend running the application through a command line as I have embedded print statements that clearly describe this application's execution process.

Does this code correctly exemplify the singleton implementation (a method of ensuring only one instance of a class can exist at a time)?

Is this the recommended, or at least an acceptable, way of ensuring only one instance of SomeClass can be instantiated at all times?

Do I need to reset SomeClass's initialized variables manually? It looks to me like the QApplication.quit() method does not call the __init__ method again and that I must manually reset the instanceVariables before quitting, otherwise they will have their last known value when I reuse the same instance of SomeClass

Do I need to perform any other cleanup before calling QApplication.quit() or will Python's garbage collector handle this for me?

Since SomeClass quits out of itself after the doStuff() method, do I need to explicitly close the instance of it in SomeOtherClass when I am finished with it? Or can I just close SomeOtherClass and that will handle everything for me?

Is it OK to reuse this instance after it's been quit out of using QApplication.quit() like this?

Would it be more efficient not to close SomeClass until SomeOtherClass has finished executing rather than opening and closing SomeClass every time I need to use its methods? Bearing in mind that the project I am working on will need to frequently use SomeClass's methods.

import sys
from PyQt5.QtWidgets import QApplication, QWidget

class SomeClass(QWidget):
    
    # Stores any existing instances of this class to prevent multiple instances being executed simultaneously, a.k.a simple singleton implementation
    instance = None
    
    def __new__(self):
        """ Method that manages the different instances of this class being created, ensuring only one instance exists at any given time """
        # If not instances already exists, creates a new instance of this class and stores it in the 'instance' attribute
        if not self.instance:
            print('Successfully created a new instance of SomeClass...')
            # Creates a new instance of this class and stores it in the 'instance' attribute
            self.instance = super().__new__(self)
        else:
            print('An instance of SomeClass already exists! Fetching that instance...')
        # Returns either the newly created instance of this class or if one previously existed, returns that instance instead
        return self.instance
    
    def __init__(self):
        # Calls the parent super class to ensure default initialization is executed
        super().__init__()
        """ Method that creates/initializes any instance variables that this class may require """
        print('Initializing SomeClass\'s instance variables...')
        # Initializes two instance variables
        self.instanceVariable1 = None
        self.instanceVariable2 = None
        print(f'instanceVariable1 = {self.instanceVariable1}, instanceVariable2 = {self.instanceVariable2}')
        
    def doStuff(self):
        """ Method that changes instance variables then quits QApplication"""
        print('Doing stuff...')
        # Manipulates the instance variables created earlier
        self.instanceVariable1 = not None
        self.instanceVariable2 = not None
        # Closes this instance
        self.closeApp()       

    def doMoreStuff(self):
        """ Method that does more stuff even though this instance was closed earlier"""
        print('Successfully doing more stuff even though this instance was previously closed...')
        # Closes this instance
        self.closeApp()        

    def closeApp(self):
        print(f'instanceVariable1 = {self.instanceVariable1}, instanceVariable2 = {self.instanceVariable2}')
        print('Quitting out of SomeClass\'s instance...')
        # Exits event loop closing this QWidget application
        QApplication.quit()
    

class SomeOtherClass():
    
    def __init__(self):
        """ Method that creates an instance of SomeClass(), does stuff, then quits out of the QApplication """
        print('Attempting to create an instance of SomeClass...')
        # Creates an instance of SomeClass()
        self.instantiatedInstanceOfSomeClass = SomeClass()
        # Does stuff in instanced version of SomeClass then quits out of SomeClass's instance
        self.instantiatedInstanceOfSomeClass.doStuff()
        # Does more stuff with the instance that was closed in the last execution
        self.reuseClosedInstance()
        
        print('Finished using both classes... Happy Coding!')
        sys.exit()
        
    def reuseClosedInstance(self):
        """ Method that does something with the instance that has already been closed"""
        print('Attempting to do stuff with the instance that was previously closed')
        # Uses the inherited instance of SomeClass to doMoreStuff even though the doStuff() method quit out of the QApplication instance
        self.instantiatedInstanceOfSomeClass.doMoreStuff()
        

def main():
    # Creates a QApplication instance in order to execute the QWidget
    eventLoop = QApplication([])
    
    # Creates an instance of SomeOtherClass which will create an instance of SomeClass, do stuff, then quits the QApplication
    inheritingClass = SomeOtherClass()
    # Access the SomeClass instance created by SomeOtherClass
    inheritedInstanceOfSomeClass = inheritingClass.instantiatedInstanceOfSomeClass
    
    # Begins the SomeClass event loop
    eventLoop.exec_()

# Executes the main method when executed via the command line
if __name__ == "__main__":
    main()
Reply
#2
I don't like your singlton implementation.
from PySide6.QtWidgets import QMainWindow, QApplication, QPushButton


class MyWindow(QMainWindow):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = super().__new__(cls)
        return cls.instance

    def __init__(self):
        super().__init__()
        btn = QPushButton("Press me", self)
        btn.clicked.connect(self.close)
        self.setCentralWidget(btn)


app = QApplication()
x = MyWindow()
y = MyWindow()
x.show()
app.exec()
Error:
super().__init__() RuntimeError: You can't initialize an object twice!
This works ok for only creating one object, but it doesn't protect against calling __init__ multiple times for that one object.

For classes where __init__() must be supported it is common to use a singleton metaclass to redirect calling the class so only one instance can be constructed.
from PySide6.QtCore import QObject
from PySide6.QtWidgets import QMainWindow, QApplication, QPushButton


class QSingleton(type(QObject)):
    """Metaclass for Qt classes that are singletons."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.instance = None

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = super().__call__(*args, **kwargs)
        return self.instance


class MyWindow(QMainWindow, metaclass=QSingleton):
    def __init__(self, *args, **kwargs):
        print("Somebody called __init__()")
        super().__init__(*args, **kwargs)
        btn = QPushButton("Press me", self)
        btn.clicked.connect(self.close)
        self.setCentralWidget(btn)


app = QApplication()
x = MyWindow()
y = MyWindow()
x.show()
app.exec()
Output:
Somebody called __init__()
Even though we try to make two MyWindows, only one is created, and the __init__() is only called once.

Quote:Is it OK to reuse this instance after it's been quit out of using QApplication.quit() like this?
I can think of no reasons to do this, but that doesn't mean they don't exist. Qt doesn't seem to mind.
from PySide6.QtCore import QObject
from PySide6.QtWidgets import QMainWindow, QApplication, QPushButton
from time import sleep


class QSingleton(type(QObject), type):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.instance = None

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = super().__call__(*args, **kwargs)
        return self.instance


class MyWindow(QMainWindow, metaclass=QSingleton):
    def __init__(self, *args, **kwargs):
        print("Somebody called __init__()")
        super().__init__(*args, **kwargs)
        btn = QPushButton(str(id(self)), self)
        btn.clicked.connect(self.close)
        self.setCentralWidget(btn)


app = QApplication()
x = MyWindow()
x.show()
app.exec()
sleep(1)
y = MyWindow()
y.show()
app.exec()
Run this example and you should see no difference between the first and second time app is used. Notice the ojbect ID in the window pushbutton is the same for both x and y. This is because x and y are the same MyWindow instance.
BrewBarred likes this post
Reply
#3
Broo, that was awesome! Thank you for such a detailed response. Finally getting some answers!

That was a really cool idea using that click button to visually show me that it's the exact same object we are referencing.

I ran your code and this has helped me learn so much! I did not know about the __call__ method and if I understand correctly, even if I initialize class scope attributes in this instance with value A for example, then change it later in the code to value B, when I quit this instance and call it again, that attribute still reads value B.

I was not expecting this at all which is why I'm glad I caught it before writing this implementation in my project.

Where would you recommend resetting my attributes to ensure they are always correctly initialized with their default values? or is there another trick I can do to kind of reset the instance to it's original state?

Quote:Is it OK to reuse this instance after it's been quit out of using QApplication.quit() like this?
I can think of no reasons to do this, but that doesn't mean they don't exist. Qt doesn't seem to mind.?

The project I am building will be using a QApplication QWidget class to draw some overlays to my screen. This will be called multiple times through out the program I write but there may be times where I don't need any overlays drawn for a few minutes.

So my natural instinct was to close that instance since it's not needed during that downtime, but perhaps it's a better idea just to leave it open the whole time? I'm always after minimizing the memory I am occupying where possible but still have a very small understanding of memory.

I also had someone explain to me that due to the nature of how QWidget works, there is no need to implement a singleton method because only one instance of QWidget can exist at a time?


To reiterate, my response questions are:

1. Where would you recommend resetting my attributes to ensure they are always correctly initialized with their default values? or is there another trick I can do to kind of reset the instance to it's original state when it's recalled?

2. Is it better just to leave it open while it's not required? or to close it and reopen it as I need it?

3. Do you think any of this is necessary if my class inherits QWidget?

Thanks again for putting in that time to even write examples of your answer, I've spent a couple days trying to figure this out across multiple sources and you are the first person to give me some useful information back, really made my day <3
Reply
#4
Don't solve problems that don't exist. Get your program running and fix the real problems you encounter. For example, I see no reason to implement a singleton pattern when you get the same tesult from just creating one instance. Most GUI programs have one main window, but few make it a singleton.
BrewBarred likes this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
Video [PyQt] Get a executable file (.exe) from a .py file add a promoted class in a QWidget MiguelonReyes 0 662 Oct-17-2023, 11:43 PM
Last Post: MiguelonReyes
  Upload file data on Qwidget maiya 4 1,436 Jul-08-2022, 08:34 AM
Last Post: Axel_Erfurt
  Made new instance - button still goes to old instance nanok66 6 3,003 Nov-06-2020, 07:44 PM
Last Post: deanhystad

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020