Question

I've been working on some debugging/testing infrastructure for my PyQt-based project. I'm tagging most widgets with special debugging attributes, even if the widgets were created on the C++ side.

However, multiple accesses to the same button in a QFileDialog seem to result in new sip wrapper instances. That is, if I access the 'Cancel' button more than once, PyQt doesn't return the same Python object each time.

Here's a minimal test program you can use to reproduce the issue:

from PyQt4.QtCore import QTimer
from PyQt4.QtGui import QApplication, QFileDialog, QDialogButtonBox

def print_debug_stuff():
    # Find the dialog from among the top-level widgets
    dlg = filter( lambda w: isinstance(w, QFileDialog), QApplication.topLevelWidgets() )[0]

    # Find the button box in the dialog, then find the cancel button
    buttonBox = dlg.findChild(QDialogButtonBox, "buttonBox")
    cancelButton = buttonBox.children()[2]

    # Does it have our debug attribute? Can we add it?
    print "Tagging button: '{}' ({})".format( cancelButton.text(), cancelButton )
    print "...had my_debug_tag?", hasattr(cancelButton, "my_debug_tag")

    # Add the debug attribute
    buttonBox.children()[2].my_debug_tag = "hello"
    print "...has my_debug_tag?", hasattr(cancelButton, "my_debug_tag")

app = QApplication([])

# Repeatedly print debug info until the program quits
timer = QTimer(timeout=print_debug_stuff)
timer.setInterval( 100.0 )
timer.start()

# Open a (non-native!) file save dialog
QFileDialog.getSaveFileName(options=QFileDialog.DontUseNativeDialog)

I would expect this to program to output something like this:

Tagging button: 'Cancel' (<PyQt4.QtGui.QPushButton object at 0x1013c8cb0>)
...had my_debug_tag? False
...has my_debug_tag? True
Tagging button: 'Cancel' (<PyQt4.QtGui.QPushButton object at 0x1013c8cb0>)
...had my_debug_tag? True
...has my_debug_tag? True
Tagging button: 'Cancel' (<PyQt4.QtGui.QPushButton object at 0x1013c8cb0>)
...had my_debug_tag? True
...has my_debug_tag? True

...but instead, here's what I actually see. Note that the cancelButton object has a different address each time, and the debugging attribute I added is lost:

Tagging button: 'Cancel' (<PyQt4.QtGui.QPushButton object at 0x1013c8cb0>)
...had my_debug_tag? False
...has my_debug_tag? True
Tagging button: 'Cancel' (<PyQt4.QtGui.QPushButton object at 0x1013c8ef0>)
...had my_debug_tag? False
...has my_debug_tag? True
Tagging button: 'Cancel' (<PyQt4.QtGui.QPushButton object at 0x1013c8e60>)
...had my_debug_tag? False
...has my_debug_tag? True
Tagging button: 'Cancel' (<PyQt4.QtGui.QPushButton object at 0x1013c8dd0>)
...had my_debug_tag? False
...has my_debug_tag? True

So it seems like PyQt is "losing" the Python object it created previously, and is then making a "new" one for each repeated access. Is that expected?

BTW, this test program behaves the same on both Mac OS X (10.7) and Linux (Ubuntu 12). I'm using PyQt 4.8.5.

Was it helpful?

Solution

The button you are attempting to reference is an internal Qt object which does not have a corresponding python equivalent. So naturally PyQt will have to create a new python wrapper for it every time you access it. And if you do print dlg, you will see that a new wrapper is created for that, too.

Given this, it should be obvious that tagging the python wrapper is not going to work, because the wrapper will be garbage-collected immediately after it goes out of scope.

To work around this, you need to tag the internal Qt object itself. One simple way to do this, would be to use properties:

    prop = cancelButton.property("my_debug_tag")
    print "...had my_debug_tag?", p.isValid()

    cancelButton.setProperty("my_debug_tag", "hello")

    prop = cancelButton.property("my_debug_tag")
    print "...has my_debug_tag?", p.isValid()

Obviously, this technique will only work for Qt classes that inherit QObject.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top