Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object going out of scope and being garbage collected in PySide/PyQt

I'm having issues with a non-Qt object falling out of scope and being garbage collected by Python. In the generic example below, I create an object when one button is clicked and want to use that object when the other button is clicked. After the slot for the 1st button is finished executing, the object is garbage collected.

# Started from http://zetcode.com/gui/pysidetutorial/firstprograms/ and
# connections program from chap 4 of Summerfield's PyQt book

import sys
from PySide import QtGui


def createThingy():
    thing1 = thingy("thingName", 3, wid)
    lbl1.setText(thing1.name + " created.")
    return


def updateNum():
    lbl1.setText("number: " + str(thing1.number))

    return


class thingy(object):
    def __init__(self, name, number, mainWindow):
        self.name = name
        self.number = number

    def func1(self):
        return "Something to return"

    def rtnNum(self):
        return "Num: " + str(self.number)

app = QtGui.QApplication(sys.argv)

wid = QtGui.QWidget()
wid.resize(250, 150)
wid.setWindowTitle('Example')

btn1 = QtGui.QPushButton("Create thingy")
btn2 = QtGui.QPushButton("Number")
lbl1 = QtGui.QLabel("---")

vlayout = QtGui.QVBoxLayout()
vlayout.addWidget(btn1)
vlayout.addWidget(btn2)
vlayout.addWidget(lbl1)
wid.setLayout(vlayout)

btn1.clicked.connect(createThingy)
btn2.clicked.connect(updateNum)

wid.show()
wid.raise_()
sys.exit(app.exec_())

This sort of behavior is mentioned on the PySide pitfalls page, https://wiki.qt.io/PySide_Pitfalls, as well as the question "Python PySide (Internal c++ Object Already Deleted)" https://stackoverflow.com/a/5339238/2599816 (this question doesn't really answer my question since the author's solution is to avoid the problem).

Because the object is not a Qt object, passing it a parent is not a viable option. Additionally, I do not want to create the object at the start of the program as some other questions/answers have suggested elsewhere (can't find links to them again). Further, the object should not be modified to solve this problem but the solution should be implemented in the GUI, i.e. keep the object generic for use as a library in other programs.

  • How do I make the object an attribute of my window or widget as suggested by the pitfalls page?
  • While making it an attribute of the window/widget is an option, is there a better design/more elegant solution/best practice to use?

Thanks for the help.

like image 419
user2599816 Avatar asked Aug 12 '16 18:08

user2599816


2 Answers

It is being garbage collected because the scope is lost once the function execution ends. You need to save the reference, and there are two ways of doing it:

  1. Save the object in a global variable, so it won't get collected by the gc and also stay accessible on the other function.
  2. Wrap the whole thing in a QWidget implementation, saving the created object in the widget object (via self).

Please let me know if I wasn't clear or accurate enough.
Hope it helps.
Cheers.

like image 68
ntocampos Avatar answered Nov 19 '22 05:11

ntocampos


Create a subclass of QWidget to keep all the child objects as attributes:

import sys
from PySide import QtGui

class Widget(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent)
        self.resize(250, 150)
        self.setWindowTitle('Example')

        self.btn1 = QtGui.QPushButton("Create thingy")
        self.btn2 = QtGui.QPushButton("Number")
        self.lbl1 = QtGui.QLabel("---")

        vlayout = QtGui.QVBoxLayout()
        vlayout.addWidget(self.btn1)
        vlayout.addWidget(self.btn2)
        vlayout.addWidget(self.lbl1)
        self.setLayout(vlayout)

        self.btn1.clicked.connect(self.createThingy)
        self.btn2.clicked.connect(self.updateNum)

    def createThingy(self):
        self.thing1 = thingy("thingName", 3, wid)
        self.lbl1.setText(self.thing1.name + " created.")

    def updateNum(self):
        self.lbl1.setText("number: " + str(self.thing1.number))

class thingy(object):
    def __init__(self, name, number, mainWindow):
        self.name = name
        self.number = number

    def func1(self):
        return "Something to return"

    def rtnNum(self):
        return "Num: " + str(self.number)

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    wid = Widget()
    wid.show()
    sys.exit(app.exec_())
like image 5
ekhumoro Avatar answered Nov 19 '22 06:11

ekhumoro