0

I have a file, Foo.py which holds the code below. When I run the file from command line using, python Foo.py everything works. However, if I use CLI of python

python 
import Foo
Foo.main()
Foo.main()
Foo.main()

The first call works fine, the second brings forward all hell of warnings, the first of which is

(python:5389): Gtk-CRITICAL **: IA__gtk_container_add: assertion 'GTK_IS_CONTAINER (container)' failed

And the last cause a segmentation fault. What's the problem with my code?

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
import os
from PyQt4 import Qt
from PyQt4 import QtGui,QtCore

class Foo (QtGui.QWidget):

    def __init__(self,parent=None):
        super(Foo,self).__init__()
        self.setUI()
        self.showMaximized()


    def setUI(self):
        self.setGeometry(100,100,1150,650)
        self.grid = QtGui.QGridLayout()
        self.setLayout(self.grid)

        #For convininece, I set different ui "concepts" in their own function
        self.setInterfaceLine()
        self.setMainText()



    def setMainText(self):
        #the main box, where information is displayed
        self.main_label = QtGui.QLabel('Details')
        self.main_text = QtGui.QLabel()
        self.main_text.setAlignment(QtCore.Qt.AlignTop)
        #Reading the welcome message from file

        self.main_text.setText('')


        self.main_text.setWordWrap(True) #To handle long sentenses
        self.grid.addWidget(self.main_text,1,1,25,8)


    def setInterfaceLine(self):

        #Create the interface section
        self.msg_label = QtGui.QLabel('Now Reading:',self)
        self.msg_line = QtGui.QLabel('commands',self) #the user message label
        self.input_line = QtGui.QLineEdit('',self) #The command line
        self.input_line.returnPressed.connect(self.executeCommand)
        self.grid.addWidget(self.input_line,26,1,1,10)
        self.grid.addWidget(self.msg_label,25,1,1,1)
        self.grid.addWidget(self.msg_line,25,2,1,7)


    def executeCommand(self):
        fullcommand = self.input_line.text() #Get the command
        args = fullcommand.split(' ')
        if fullcommand =='exit':
            self.exit()





    def exit(self):
        #Exit the program, for now, no confirmation
        QtGui.QApplication.quit()



def main():
    app = QtGui.QApplication(sys.argv)
    foo = Foo(sys.argv)
    app.exit(app.exec_())

if __name__ in ['__main__']:
    main()
Yotam
  • 10,295
  • 30
  • 88
  • 128

3 Answers3

4

I'm able to reproduce in Python 3 but not Python 2.

It's something about garbage collection and multiple QApplications. Qt doesn't expect multiple QApplications to be used in the same process, and regardless of whether it looks like you're creating a new one every time, the old one is living on somewhere in the interpreter. On the first run of your main() method, You need to create a QApplication and prevent it from being garbage collected by storing it somewhere like a module global or an attribute to a class or instance in the global scope that won't be garbage collected when main() returns.

Then, on subsequent runs, you should access the existing QApplication instead of making a new one. If you have multiple modules that might need a QApplication but you don't want them to have to coordinate, you can access an existing instance with QApplication.instance(), and then only instantiate one if none exists.

So, changing your main() method to the following works:

def main():
    global app
    app = QtGui.QApplication.instance()
    if app is None:
        app = QtGui.QApplication(sys.argv)
    foo = Foo(sys.argv)
    app.exit(app.exec_())

It's odd you have to keep a reference to ensure the QApplication is not garbage collected. Since there is only supposed to be one per process, I would have expected it to live forever even if you don't hold onto a reference to it. That's what seems to happen in Python 2. So ideally the global app line would not be needed above, but it's there to prevent this garbage collection business.

We're kind of stuck in the middle about how immortal this QApplication object is ... it's too long lived for you to be able to use a new one each time, but not long-lived enough on its own for you to re-use it each run without having to prevent its garbage collection by holding onto a reference. This might be bug in PyQt, which probably ought to do the reference holding for us.

Chris Billington
  • 855
  • 1
  • 8
  • 14
  • In principle, I don't see any reason why multiple instances of `QApplication` cannot be created, so long as no more than one exists at the same time. In fact, it may often be a *requirement* in unit-testing that a new application instance is created for each test. The important thing is to ensure that each instance gets deleted properly, and, perhaps more importantly, that it gets deleted at the *right time*. Usually, explicitly destroying the main window and/or the application with `del` will be enough to avoid problems. – ekhumoro Apr 05 '15 at 17:17
  • My code didn't work on python 2 or 3. Your answer fixes this though. – Yotam Apr 05 '15 at 17:58
  • @ekhumoro, `del` should only be required if you're in the global scope. Since all it does is delete the reference to an object, the same thing is achieved by the reference going out of scope when `main()` returns, and in either case if the reference count hits zero Python then destroys the object. Something about Qt or PyQt however means that QApplications seem to not be getting properly cleaned up when their reference counts go to zero. – Chris Billington Apr 06 '15 at 02:34
  • @ChrisBillington. The "something" is the order of deletion. This is a very well-known issue in PyQt: see [Object Destruction on Exit](http://pyqt.sourceforge.net/Docs/PyQt5/pyqt4_differences.html#object-destruction-on-exit), for instance. The reason why `del` can sometimes (but not always) work in these situations is that it provides a way to influence the *order* in which objects get deleted. There is no general solution that will work for all applications, on all systems, though. – ekhumoro Apr 06 '15 at 03:49
1

There must be only on QApplication instance in one process. GUI frameworks are not prepared for multi-application mode.

Daniel
  • 42,087
  • 4
  • 55
  • 81
1

I can't actually reproduce the problem, which at least shows there's nothing fundamentally wrong with the code.

The problem is probably caused by some garbage-collection issues when the main function returns (the order of deletion can be unpredictable).

Try putting del foo after the event-loop exits.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • In the python prompt, I typed ``del Foo`` followed by ``import Foo`` and ``Foo.main()`` with the same result. I'm using python 2.7.8 on fedora machine. Also, I'm using gnome environment, not KDE. – Yotam Apr 05 '15 at 07:27
  • @Yotam. No, I meant try putting `del foo` at the end of the `main()` function, after `app.exec_()` - i.e. explicitly delete the instance of `Foo` after the application quits. – ekhumoro Apr 05 '15 at 16:30
  • This still doesn't work, this is, however, an issue with garbage collection. Chris's answer was right. Thank you. – Yotam Apr 05 '15 at 17:34
  • neither `del foo` the `QApplication` nor `del Foo` for the module have any effect on garbage collection - the former deletes a reference moments before it is about to go out of scope anyway, so doesn't achieve anything that wasn't going to happen already. Deleting `Foo` the module just deletes a single reference to it, but since modules are cached in `sys.modules`, the reference count for the module is not zero and it does not get garbage collected. – Chris Billington Apr 06 '15 at 02:39
  • @ChrisBillington. It does have one very obvious effect: it can be used to change the order in which objects get deleted. Since I wasn't able to reproduce the problem myself, `del foo` was really just a guess: sometimes it will work, sometimes it won't. Not sure what you mean about deleting the module, though, as I didn't suggest that (I think that was just a misunderstanding by the OP). – ekhumoro Apr 06 '15 at 03:59