0

I have created a GUI that loads a library my_lib which depends on a number of heavy modules. It will be converted to an executable using PyInstaller.

I would like to create a splash screen to hide the long (more than 5 seconds) loading time.

One method is to use the --splash parameter of PyInstaller, but this will be independent of the program and works using a timer.

The other method is to create splash screen using PyQt5. Here's a sample of the main script:

# various imports including standard library and PyQt.
from my_lib import foo, bar, docs_url, repo_url


class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.action_docs.triggered.connect(lambda x: QDesktopServices.openUrl(QUrl(docs_url)))
        self.action_code.triggered.connect(lambda x: QDesktopServices.openUrl(QUrl(repo_url)))

    def show_splash(self):
        self.splash = QSplashScreen(QPixmap("path\to\splash.png"))
        self.splash.show()
        # Simple timer to be replaced with better logic.
        QTimer.singleShot(2000, self.splash.close)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create('fusion'))
    main = MainWindow()
    main.show()
    main.show_splash()
    sys.exit(app.exec_())

The problem with the above code is that the import is done at the top and then the splash screen is opened which defeats the point. Also, the definition for MainWindow class depends on the objects imported from my_lib.

How can I hide the loading time of the heavy modules when the basic definitions of the GUI depends on them? Is there I'm something missing? Is it even possible?

  • It depends on what the module is loading. You could try to create the splash and show it before loading the module (in the if block), but the loading might prevent proper painting of the image. Does the module need to be global, or can its content be accessed as a local import? A possible solution could be to connect the module loading with a QTimer that is launched when the event loop is started. – musicamante Mar 31 '22 at 07:57
  • @musicamante I'm not sure I understand correctly. But `my_lib` is used in a lot of places in the `MainWindow` class. This means that it is accessed before __ main __ conditional is even reached. – Mohammadreza Khoshbin Apr 01 '22 at 22:39
  • @musicamante Your answer gave me an idea. I posted it as an answer. – Mohammadreza Khoshbin Apr 01 '22 at 23:38

1 Answers1

1

Edit: This approach is unnecessary. As @musicamante noted in the comments, the above (edited) example raises no errors and can be used without any problems.


After considering the comment by @musicamante, I had the idea to divide the logic in the __ main __ conditional into two pieces. One before the heavy imports and the other in the end. So it goes like this:

if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create('fusion'))
    splash_object = QSplashScreen(QPixmap("path\to\splash.png"))
    splash_object.show()

#
# Load the heavy libraries, including my_lib.
#

class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

if __name__ == '__main__':
    splash_object.close()
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())

This seems to work very well, but I'm not sure about any side effects of this split.

It should be noted that much of the startup time seems to be from the unpacking of the executable file.

Edit: This answer suggests a similar approach.

  • 1
    The split is unnecessary. If it works, you can just do the above in one block, just show the splash screen *before* importing the module. While uncommon (and often frowned upon), there are instances for which loading modules in other place than the beginning of the file is fine. – musicamante Apr 02 '22 at 00:00
  • The problem is that the __ main __ conditional requires `MainWindow` which in turn depends on `my_lib`. Therefore, I cannot put the imports inside the __ main__ at the end of the file. – Mohammadreza Khoshbin Apr 02 '22 at 00:08
  • This answer suggest a similar approach: https://stackoverflow.com/a/9474575/7180705 – Mohammadreza Khoshbin Apr 02 '22 at 00:13
  • 1
    Does `MainWindow` require `my_lib` in its class definitions (class attributes, methods created on condition)? That seems strange, and was not explained in your question. If the module is only required in instance methods, you don't have that requirement. – musicamante Apr 02 '22 at 00:23
  • @musicamante Yeah. Sorry I wasn't clear enough. The GUI is a simple interface for the main library and a number of the stuff in the GUI is generated/updated based on the parent library (`my_lib`) so I can maintain them both more easily. That said, I'm not sure if that is standard practice as I am quite new to GUI development. – Mohammadreza Khoshbin Apr 02 '22 at 00:27
  • Since the GUI could only be created in an instance of a Qt widget class, what said above still stands (at least, theoretically): as long as you didn't create a class that defines *alternate* methods or class attributes depending on the state of the `my_lib`, you don't need that. To clarify: if you do different things (like calling different functions) in the `__init__` of the class depending on the state of the module, there's no need to separate those blocks, because the `__init__` will be called only when the instance is created, and, at that point, the module has been already imported. – musicamante Apr 02 '22 at 00:36
  • I'm not sure I understand. The MainWindow's `__init__` uses a lot of the properties in `my_lib` to create/update various widgets (which are instance attributes). But the main problem is that without importing, I cannot *define* the class and if I put the `__main__` conditional at the top it doesn't recognize the class. – Mohammadreza Khoshbin Apr 02 '22 at 00:48
  • 1
    "What do you mean by "I cannot define the class"? I strongly suggest you to clarify that by editing your original question and adding an *actual* [mre], because you're introducing aspects that you never mentioned in it. – musicamante Apr 02 '22 at 01:08
  • 1
    In the edited code there's nothing that says that you cannot "define" the class without previously importing the module. What's in the `__init__` is irrelevant: if you import the module *before* creating the instance, it's fine: as already said, you can more than safely create the class (not the instance!) before importing `my_lib` in your code. Have you actually tried to do it? – musicamante Apr 02 '22 at 01:42
  • I just tried it again and it works! I think the reason for my mistake was a combination of other code also requiring `my_lib` (can be rewritten), and me taking PyCharms warnings too seriously. Thanks for taking the time to help an also, you make good music! – Mohammadreza Khoshbin Apr 02 '22 at 01:56