Why crash when derive from QListWidgetItem AND QObject - python

The following minimal example crashes in pyqt 5.7.1 on windows (copy-paste this in a .py file and run):
from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QApplication
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal
class MyListItem(QListWidgetItem):
def __init__(self, obj):
QListWidgetItem.__init__(self, 'example')
obj.sig_name_changed.connect(self.__on_list_item_name_changed)
def __on_list_item_name_changed(self, new_name: str):
self.setText(new_name)
class MyListItem2(QListWidgetItem, QObject):
def __init__(self, obj):
QListWidgetItem.__init__(self, 'example')
QObject.__init__(self)
obj.sig_name_changed.connect(self.pyqt_slot)
#pyqtSlot(str)
def __on_list_item_name_changed(self, new_name: str):
self.setText(new_name)
class Data(QObject):
sig_name_changed = pyqtSignal(str)
class SearchPanel(QListWidget):
def __init__(self, parent=None):
QListWidget.__init__(self, parent)
obj = Data()
hit_item = MyListItem(obj) # OK
hit_item = MyListItem2(obj) # crashes
self.addItem(hit_item)
obj.sig_name_changed.emit('new_example')
app = QApplication([])
search = SearchPanel()
search.show()
app.exec()
Now just comment out the line that says "crashes", and it works fine. Moreover, the list widget shows 'new_example', showing that the signal went through.
Is there a way to make it work with MyListItem2? i.e. I want to be able to decorate the slot with pyqtSlot, which in turn requires (in PyQt 5.7) that I derive item from QObject.
The intent here is that each item in the list has several characteristics that can change (icon, font, text color) based on signals from associated Data instance (each instance actually "lives", in the Qt sense of the term, in a second thread of our application).

This has got nothing to do with pyqtSlot.
The actual problem is that you are trying to inherit from two Qt classes, and that is not generally supported. The only exceptions to this are Qt classes that implement interfaces, and Qt classes that share a common base-class (e.g. QListWidget and QWidget). However, only the former is offically supported, and there are several provisos regarding the latter (none of which are relevant here).
So a Python class that inherits from both QListWidgetItem and QObject just will not work. The main problem occurs when PyQt tries to access attributes that are not defined by the top-level base-class (even when the attribute does not exist). In earlier PyQt versions, this would simply raise an error:
>>> class MyListItem2(QListWidgetItem, QObject): pass
...
>>> x = MyListItem2()
>>> x.objectName()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: could not convert 'MyListItem2' to 'QObject'
>>> x.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: could not convert 'MyListItem2' to 'QObject'
which makes it clear that (in C++ terms) a MyListItem2(QListWidgetItem) cannot be cast to a QObject. Unfortunately, it seems that more recent versions of PyQt5 no longer raise this error, and instead just immediately dump core (which presumably is a bug).
If you really need to use pyqtSlot, one suggestion would be to use composition rather than subclassing. So perhaps something like this:
class ListItemProxy(QObject):
def __init__(self, item, obj):
QObject.__init__(self)
self._item = item
obj.sig_name_changed.connect(self.__on_list_item_name_changed)
#pyqtSlot(str)
def __on_list_item_name_changed(self, new_name: str):
self._item.setText(new_name)
class MyListItem2(QListWidgetItem):
def __init__(self, obj):
QListWidgetItem.__init__(self, 'example')
self._proxy = ListItemProxy(self, obj)

Related

TypeError: __init__() missing 1 required positional argument: , but I am giving the needed argument

edit : Problem solved, I was indeed a idiot and it was indeed a very silly mistake. The solution was that I forgot to give an instance of UI when creating a Joueur instance... Sorry and thanks to all who tried to help me.
I've been trying to code a chess game. I am now in the process of testing it and I got a "weird" error (from my experience I probably just messed up, but my situation is not exactly what people on every other post on this error seem to be and I have been searching my code and documentation for clues for some hours with no success : so here I am).
So to summarize the code (I won't put code I have successfully run before and will only show what should be relevant):
I have a board class which is basically my model and my controller. In the constructor I ask for arguments two players, a UI class, and a hash class. Before I implemented the last two, and added it in the constructor, the code ran just fine.
class Plateau:
def __init__(self, j1, j2, UI, Hash):
self.UI = UI
self.UI.ajoutePlateau(self)
self.Hash = Hash
self.Hash.hashPlateau(self)
# some code to deal with the rest
...
#
self.UI.initAffichage()
I then have a UI interface and a ConsolUI (UITerminal in my code) which inherit from it. It is as expected what show to the user the board and also what ask the human player (if there is one) what he wants to play.
class UI:
def __init__(self):
self.Plateau = None
def ajoutePlateau(self, Plateau):
self.Plateau = Plateau
# a bunch of method that throw an exception and are overrided by the child class
...
#
class UITerminal(UI):
def __init__(self):
#super(UITerminal, self).__init__(self)
#super().__init__(self)
#UI.__init__(self)
#super(UITerminal, self).__init__()
super().__init__()
#UI.__init__()
# the method that had to be overridden
...
#
I have also tried few version of the UITerminal constructor (which are above and put on comment).
or even just nothing since it should not even be needed (I think...).
Then there is the hash, which is build the same way as the UI : an interface, a child.
class Hash:
def __init__(self):
pass
# a bunch of method that throw an exception and are overridden by the child class
...
#
class ZombristHash(Hash):
def __init__(self):
#super(ZombristHash, self).__init__(self)
#super().__init__(self)
#Hash.__init__(self)
#super(ZombristHash, self).__init__()
super().__init__()
#Hash.__init__()
# a bunch of code to init the zombristhash
...
#
same as with the UI, I tried multiple way to call the interface constructor.
Then I have my main, which is only 1 line and is what throw the error :
p = Plateau(Humain("j1"), Humain("j2"), UITerminal(), ZombristHash())
and the error is :
Traceback (most recent call last): File "plateau.py", line 298, in <module> p = Plateau(Humain("j1"), Humain("j2"), UITerminal(), ZombristHash())
TypeError: __init__() missing 1 required positional argument: 'UI'.
From what I understand, he tells me I haven't given the board constructor a UI as argument, but I did so I do not understand what is happening.
I tried on quamran's suggestion this :
p = Plateau(None, None, None, None)
And it seems to think it was ok now...
Traceback (most recent call last):
File "plateau.py", line 298, in <module>
p = Plateau(None, None, None, None)
File "plateau.py", line 13, in __init__
self.UI.ajoutePlateau(self)
AttributeError: 'NoneType' object has no attribute 'ajoutePlateau'
As #Jacques Gaudin noticed, you need to change this :
class ZombristHash(Hash):
def __init__(self):
Hash.__init__()
to this :
class ZombristHash(Hash):
def __init__(self):
super().__init__()
and same for UI.
Finally, you get something like this :
class Plateau:
def __init__(self, j1, j2, UI, Hash):
self.UI = UI
self.UI.ajoutePlateau(self)
self.Hash = Hash
class Hash:
def __init__(self):
pass
class ZombristHash(Hash):
def __init__(self):
super().__init__()
class UI:
def __init__(self):
self.Plateau = None
def ajoutePlateau(self, Plateau):
self.Plateau = Plateau
class UITerminal(UI):
def __init__(self):
# super(UITerminal, self).__init__()
super().__init__()
class Humain():
def __init__(self, j):
pass
p = Plateau(Humain("j1"), Humain("j2"), UITerminal(), ZombristHash())

Multiply inherit from QWidget and another base class?

I am trying to create a PyQt5.QtWidgets.QWidget derived widget, whilst at the same time inherit from another base class, Base.
Inheriting from Base causes an error when calling the QWidget constructor, saying I never provided the arguments which Base requires.
This is how I attempt to call the QWidget and Base constructors:
class Deriv(QtWidgets.QWidget, Base):
def __init__(self):
QtWidgets.QWidget.__init__(self)
Base.__init__(self, "hello world")
The error I'm getting says:
QtWidgets.QWidget.__init__(self)
TypeError: __init__() missing 1 required positional argument: 'id'
Is it possible to inherit from both QWidget and Base, and if so, how do I call their respective constructors correctly?
Here is an exemplar app which reproduces the error I am experiencing:
#!/usr/bin/env python3
import sys
from PyQt5 import QtWidgets
class Base(object):
def __init__(self, id):
self.id = id
class Deriv(QtWidgets.QWidget, Base):
def __init__(self):
QtWidgets.QWidget.__init__(self)
Base.__init__(self, "hello world")
self.show()
def main():
app = QtWidgets.QApplication(sys.argv)
d = Deriv()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here is the traceback:
Traceback (most recent call last):
File "./test.py", line 22, in <module>
main()
File "./test.py", line 18, in main
d = Deriv()
File "./test.py", line 11, in __init__
QtWidgets.QWidget.__init__(self)
TypeError: __init__() missing 1 required positional argument: 'id'
Note that if I only inherit from QWidget (so just remove everything referring to Base), then the code works
class Deriv(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.show()
I'm assuming QtWidgets.QWidget.__init__ itself uses super to call __init__. With your single inheritance case, the MRO consists of three classes: [Deriv, QtWidgets.QWidget, object]. The call super(QtWidgets.QWidget, self).__init__ would call object.__init__, which doesn't expect any additional arguments.
In your multiple inheritance example, the MRO looks like
[Deriv, QtWidgets.QWidget, Base, object]
which means now the call super(QtWidgets.QWidget, self).__init__ refers not to object.__init__, but Base.__init__, which does require an additional argument, leading to the error you see.

PyQT trouble passing filenames across QWizardPage

I am trying to have the user upload a file to the application on one QWizardPage, and then be able to re-use that same file path on another QWizardPage. However, from my code
class ExecutePage(QtWidgets.QWizardPage):
def __init__(self,*args,**kwargs):
super().__init__()
def initializePage(self):
self.setTitle("Choose file to execute")
self.setSubTitle("Please choose a file to execute")
self.myTextBox = QtWidgets.QTextEdit(self)
self.myTextBox.setGeometry(QtCore.QRect(100, 0, 350, 50))
self.uploader = QtWidgets.QPushButton("upload",self)
self.uploader.clicked.connect(self.get_file_name)
def get_file_name(self):
self.name = QtWidgets.QFileDialog.getOpenFileName(self.uploader,'Choose File to Run')[0]
self.registerField("file_name",self.name,"currentText")
class ConclusionPage(QtWidgets.QWizardPage):
def __init__(self,*args,**kwargs):
super().__init__()
def initializePage(self):
self.setSubTitle(self.field("file_name"))
I am getting an error
TypeError: registerField(self,str,QWidget,property: str = None,
changedSignal: PYQT_SIGNAL = 0): argument 2 has unexpected type 'str'
Is there a simple way to convert this specific string (self.name) into a QWidget that is able to be passed onto other pages in the Wizard (i.e. in this example, onto the subtitle field in the Conclusion Page)?
I've looked through the documentation but can't figure it out so would appreciate a few pointers. Thanks.
You can only use registerField() method to pass a qproperty to the QWidget, in the case of QFileDialog it is not possible since there is no q-property associated with the selection also getOpenFileName() is a static method and getting the object is a complicated task, There are 2 possible solutions, the first is to create a class that inherits from QFileDialog and has a qproperty associated with the selection or use the magic of python to pass the values. The last one is the method I will use.:
class ExecutePage(QtWidgets.QWizardPage):
...
def get_file_name(self):
name, _ = QtWidgets.QFileDialog.getOpenFileName(self.uploader,'Choose File to Run')
self.wizard().file_name = name
class ConclusionPage(QtWidgets.QWizardPage):
...
def initializePage(self):
if hasattr(self.wizard(), 'file_name'):
self.setSubTitle(self.wizard().file_name)

PySide.QtGui RuntimeError: '__init__' method of object's base class not called ...but it was

Some environment basics
Python Version: 3.4.2
OS: Windows 8.1
Searching so far, I suspect this other question is related to my issue at hand, but I'm not sure how I'm replicating enough of the same conditions--probably my lack of in-depth python knowledge.
Simplified code to reproduce issue:
Base Class
from PySide.QtGui import *
class Interface(QWidget):
'''
Wrapper base class for GUI input QWidgets:
- buttons
- text fields
- checkboxes
- line edit
- dropdown menu (combo box)
'''
def __init__(self, parent, name, title_txt=None, qt_obj=None,
update_log_method=None):
print('Interface base class constructor has been called.') #DEBUG
self._parent = parent
self.title = None
self.name = name #also text that appears on component
self.qt_obj = qt_obj
self.inheritted_log_method = update_log_method
# don't want to create an empty text QLabel, or one with
# the text reading "None".
if title_txt:
self.title = QLabel(text=title_txt, parent=parent)
print('Interface base class constructor has been completed.') #DEBUG
def get_name(self):
return self.name
def update_log(self, message, level="INFO"):
''' '''
self.inheritted_log_method(message, level)
Inheriting Class
class IFPushButton(Interface):
''' '''
def __init__(self, name, parent, icon=None, update_log_method=None):
''' '''
# print('\n\nCHECKPOINT: pre IFPushButton super()\n\n') #DEBUG
super(IFPushButton, self).__init__(
parent=parent,
name=name,
qt_obj=QPushButton(icon, name, parent),
update_log_method=update_log_method)
self.behaviors = {}
self.qt_obj.clicked.connect(self.activate)
Something to kick it all off
if __name__ == '__main__':
# setup
import sys
app = QApplication(sys.argv)
qmw = QMainWindow()
qcw = QWidget() #central widget
qcl = QVBoxLayout(qcw) #central layout
# experimental
name = 'named button'
ifpb = IFPushButton(name=name, parent=None, icon=None, update_log_method=None)
print("as long a I don't touch the ifpb instance, everything seems to be okay.")
print("...but the second I do...")
qcl.addWidget(ifpb)
self.show()
print("name of created push button:", ifpb.get_name())
# proper teardown
sys.exit(app.exec_())
I run this all inside one module, interface.py, and when I run it...
C:\Path\To\Module> python interface.py
Interface base class constructor has been called.
Interface base class constructor has been completed.
as long a I don't touch the ifpb instance, everything seems to be okay.
...but the second I do...
Traceback (most recent call last):
File "c_interface.py", line 167, in <module>
qcl.addWidget(ifpb)
RuntimeError: '__init__' method of object's base class (IFPushButton) not called.
The part that confuses me is how the print statements in the base class, Intefrace, are obviously being called as they are printing--but it still raises a RuntimeError saying that it hasn't been initialized, and of course fails to get so far as to create the app window. Most of the related messages I've found on stackoverflow are related to people initializing things incorrectly with the super() method--but I have quintuple-checked my super inits, and everything I see tells me it should be working, with the exception of what I linked above.
If I could understand more about why this is happening I'm hoping I can find a way to work around it. Any assistance is much appreciated--thanks in advance!
In the meantime I'm going to try to find how I might be unintentionally deepcopy-ing a C++ object...
EDIT: included the url in the link to other stack overflow post.
Adding a super call to the Interface class constructor is required:
def __init__(self, parent, name, title_txt=None, qt_obj=None, update_log_method=None):
super(Interface, self).__init__(parent)
...
Also, you're calling self.show(), where you probably mean qmw.show().

Problem with SIGNAL - SLOT, aboutToQuit()

My application exits only by right-clicking the tray icon, and pressing "Quit":
class DialogUIAg(QDialog):
...
self.quitAction = QAction("&Quit", self, triggered=qApp.quit)
The Module below is the application's starting point:
#!/usr/bin/env python
import imgAg_rc
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import appLogger
from runUIAg import *
class Klose:
""" Not sure if i need a Class for it to work"""
def closingStuff(self):
print("bye")
#pyqtSlot()
def noClassMethod():
print("bye-bye")
app = QApplication(sys.argv)
QApplication.setQuitOnLastWindowClosed(False)
k = Klose()
app.connect(app, SIGNAL("aboutToQuit()"), k,SLOT("closingStuff()")) #ERROR
app.connect(app, SIGNAL("aboutToQuit()"), k.closingStuff) # Old-Style
app.connect(app, SIGNAL("aboutToQuit()"), noClassMethod) # Old-Style
app.aboutToQuit.connect(k.closingStuff) # New-Style
app.aboutToQuit.connect(noClassMethod) # New-Style
winUIAg = DialogUIAg()
winUIAg.show()
app.exec_()
My intention is to execute a block of code, when the application is aboutToQuit.
This is the Error i am getting:
$ ./rsAg.py
Traceback (most recent call last):
File "./rsAgent.py", line 20, in <module>
app.connect(app, SIGNAL("aboutToQuit()"), k,SLOT("closingStuff()"))
TypeError: arguments did not match any overloaded call:
QObject.connect(QObject, SIGNAL(), QObject, SLOT(), Qt.ConnectionType=Qt.AutoConnection): argument 3 has unexpected type 'Klose'
QObject.connect(QObject, SIGNAL(), callable, Qt.ConnectionType=Qt.AutoConnection): argument 3 has unexpected type 'Klose'
QObject.connect(QObject, SIGNAL(), SLOT(), Qt.ConnectionType=Qt.AutoConnection): argument 3 has unexpected type 'Klose'
I am new to python and Qt and i would appreciate your help.
EDIT:
I forgot to mention versions (python: 3.2, pyQt: 4.8.4)
We dont need a class to define a Slot. Any method can be a Slot, by using the #pyqtSlot() decorator.
I added noClassMethod() in the code.
#Mat , your suggestion helped me go further. Now i found 3 other ways of doing it. I guess its about old-style vs new-style.
I will not delete the Error message, for possible future readers.
Thanks to everyone :-)
The PyQt signal/slot syntax isn't entirely identical to the C++ one.
Try with:
class Klose:
def closingStuff(self):
print("bye")
...
app.connect(app, SIGNAL("aboutToQuit()"), k.closingStuff)
Not sure it is necessary in PyQt, but signals and slots are generally expected to come from/go to QObjects. New-style signals and slots could be of interest if your version of PyQt is recent enough.
In PyQt5, new-style signal: app.aboutToQuit.connect(...)
def app_aboutToQuit():
print('app_aboutToQuit()')
app = QtWidgets.QApplication(sys.argv)
app.aboutToQuit.connect(app_aboutToQuit)

Categories