How to prevent a RuntimeError when unpickling a QObject? - python

Trying to unpickle a pickled QObject (using Python 2.7, PyQt4 or 5, pickle or cPickle), raises the following RuntimeError:
RuntimeError: super-class __init__() of type QObject was never called
A minimal example:
cPickle.loads(cPickle.dumps(QtCore.QObject(), cPickle.HIGHEST_PROTOCOL))
I am aware that unpickling an object, by design, does not call the object's __init__() method.
How, then, can I make sure the superclass __init__() is called, in this case?
A seemingly similar question was asked here, but was not answered.

One possible solution, based on this answer, can be implemented as follows (a slightly more elaborate example, with a custom attribute):
import cPickle
from PyQt5 import QtCore
class MyQObject(QtCore.QObject):
def __init__(self, parent=None):
super(MyQObject, self).__init__(parent)
# Add some custom attribute
self.some_attribute = 'something'
def __setstate__(self, state):
# Restore attributes
self.__dict__.update(state)
# Call the superclass __init__()
super(MyQObject, self).__init__()
original = MyQObject()
pickle_string = cPickle.dumps(original, cPickle.HIGHEST_PROTOCOL)
restored = cPickle.loads(pickle_string)
The parent can then be set using setParent(), if necessary.

Related

Click a button and change MainWindow

I am learning Qt and I am interesting in how to change some features of MainWindow.
I was trying this code, but there were some errors when I clicked the first button:
Traceback (most recent call last):
File "\main.py", line 15, in run_the_first_button_was_clicked
the_first_button_was_clicked(self)
File "clickedButton.py", line 15, in the_first_button_was_clicked
self.button2.clicked.connect(self.the_second_button_was_clicked)
AttributeError: 'MainWindow' object has no attribute 'the_second_button_was_clicked'
what I did wrong (how could I do 'the_second_button_was_clicked' callable )?
main.py
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton
import sys
from clickedButton import the_first_button_was_clicked, the_second_button_was_clicked
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("MainWindow")
self.button1 = QPushButton("button1")
self.button1.clicked.connect(self.run_the_first_button_was_clicked)
self.setCentralWidget(self.button1)
def run_the_first_button_was_clicked(self):
the_first_button_was_clicked(self)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
clickedButton.py
from PySide2.QtWidgets import QPushButton
from PySide2 import QtCore
def the_first_button_was_clicked(self):
self.setWindowTitle("the_first_button_was_clicked next will be the_second_button_was_clicked")
self.resize(800, 600)
self.button1.setText("the_first_button_was_clicked")
self.button1.setEnabled(False)
self.button2 = QPushButton("button2")
self.button2.setGeometry(QtCore.QRect(100, 100, 150, 150))
self.button2.setVisible(True)
self.button2.clicked.connect(self.the_second_button_was_clicked)
def the_second_button_was_clicked(self):
self.setWindowTitle("the_second_button_was_clicked")
self.resize(600, 800)
The issue has nothing to do with PyQt, but with how classes and instances work.
The first argument of instance methods always refers to the instance of the class, and it's called self just for convention: it could actually be named in any way as long as its syntax is valid, just like any other variable.
When using functions that are declared outside a class, it's good practice to avoid that naming convention (mostly to avoid confusion when reading code).
What is happening is that the self in def the_first_button_was_clicked(self): refers to the instance of MainWindow, which has no the_second_button_was_clicked method, hence the AttributeError exception.
The point is that both your functions are just functions, not methods (which are functions of an instance or a class): they are not members of the class.
Also note that creating a direct connection to the function will not work, as the self argument is only "created" when a function is a method.
As Heike pointed out in the comments, a possibility is to use lambda, which allows keeping an actual reference to the instance, while directly calling the function, which will be executed using the self argument provided, exactly as you did in run_the_first_button_was_clicked.
In the following examples I'm replacing self with mainWinInstance in order to make things more clear (which is the reason for which self should not be used in these cases).
def the_first_button_was_clicked(mainWinInstance):
# we reference the function locally to this script, adding the "self" argument
mainWinInstance.button2.clicked.connect(lambda:
the_second_button_was_clicked(mainWinInstance))
def the_second_button_was_clicked(mainWinInstance):
# "self" (mainWinInstance) was manually added to the function arguments
mainWinInstance.setWindowTitle("the_second_button_was_clicked")
mainWinInstance.resize(600, 800)
Another possibility is to make the second function a member of the instance:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# ...
self.the_second_button_was_clicked = the_second_button_was_clicked
or:
def the_first_button_was_clicked(mainWinInstance):
# ...
mainWinInstance.the_second_button_was_clicked = the_second_button_was_clicked
mainWinInstance.button2.clicked.connect(mainWinInstance.the_second_button_was_clicked)
In both cases the instance attribute has to be created before the connection (which also means before calling the first function in the first case).
Consider that this "monkey patching" approaches should only be used in special cases (mostly due to objects that cannot be subclassed because created autonomously), especially if done outside the class or even the script.
In most cases, what you're doing is considered bad practice, and if you're doing this with a class created on your own, there's probably something really wrong in your implementation: you should better rethink your logic and implement everything within the class itself.

How to set the type of a pyqtSignal (variable of class X) that takes a X instance as argument

I have a pyqtSignal as a class variable and I want its argument to be an instance of that class. How can I declare the type of the argument when I create the signal?
class MyClass(QWidget):
my_signal = pyqtSignal(?)
(...)
def emit_signal(self):
self.my_signal.emit(self)
I know that pyqtSignal(object) would work but I was wondering if there was a sort of __class__ at class-variable level.
Another question: is there a difference between pyqtSignal(MyClass), pyqtSignal(QWidget) (parent class) or pyqtSignal(object)? Performance? I'm asking this question because the types of slot parameters don't seem to matter when we declare them with the #pyqtSlot decorator.
At the moment the Python interpreter interprets my_signal = pyqtSignal(...), the class name is not known to it yet, the class body is created first then it will be assigned to the module variable called MyClass. So the only way to do this is after the class has been defined, and before any attempt is made to access the signal object. This means one of the following two options might work (it depends on how pyqtSignal and QObject work, I can't try it out at this moment):
At module level, after class:
class MyClass(QObject):
def __init__(self, ...):
...
MyClass.my_signal = pyqtSignal(MyClass)
In the init, guarding against re-creation:
class MyClass(QObject):
def __init__(self, ...):
if not hasattr(self, 'my_signal'):
MyClass.my_signal = pyqtSignal(MyClass)

Python subclassing issue

I'm having an issue with a python subclass :
I have my own class, Player which extends a MediaPlayer class defined in an external module.
MediaPlayer extends _Ctype and implements the __new__ method.
Below is the code of my class :
import vlc
from PyQt5.QtCore import QObject
from core import Media
class Player(vlc.MediaPlayer, QObject):
def __new__(cls, *args):
return super(Player, cls).__new__(cls, *args)
def __init__(self, *args):
super(Player, self).__init__(self, None)
def setMedia(self, location=None):
print(self)
media = Media.Media(location)
print("Player.setMedia")
self.set_media(media)
And when I call
player.setMedia([file_path]) #player is supposed to be an instance of the Player class
I get the following error:
AttributeError: 'MediaPlayer' object has no attribute 'setMedia'
The issue seems to be that when I instanciate the Player class it returns a MediaPlayer instance and therefore I can't use the setMedia method.
So I was wondering if any of you had any inkling why that issue occured and how to solve it.
Thank you for reading my post so far.
I use Python 3.4.2 64bit on Windows 8.1 64bit.
I don't entirely understand the ctypes code of the vlc module, but it sure looks like it doesn't properly support inheritance. The return value of MediaPlayer.__new__ is always an instance of MediaPlayer, rather than an instance of the type that gets passed in as the cls arg. I imagine this is a bug.
I'm not sure if you can work around this directly. One possible solution is to try fixing up the class of the object you get back in __new__:
def __new__(cls, *args):
self = super(Player, cls).__new__(cls, *args)
self.__class__ = cls
return self
I don't know if this will work correctly (or at all). The __class__ attribute is not always writable in objects built in C code, and even if it can be written to, it might break things.
A further issue is that the second base type, QObject, does not get its __new__ method called by MediaPlayer.__new__. If QObject does anything special there, the instance you get after changing the __class__ variable may still not be in a sane state. Usually you need all of your base classes to support cooperative multiple inheritance if you're going to do anything complicated with more than one base (mixins are perhaps an exception), but in this case you have at least one base that doesn't even support single inheritance properly.
Another, perhaps more foolproof solution would be to abandon inheritance (at least for the MediaPlayer part of your Player), and just create a reference to a MediaPlayer instance in __init__:
class Player(QObject):
def __init__(self, *args):
super().__init__(*args) # pass args to QObject?
self.media_player = MediaPlayer(*args) # and to MediaPlayer as well?
def setMedia(self, location=None):
print(self)
media = Media.Media(location)
print("Player.setMedia")
self.media_player.set_media(media)
If you're calling MediaPlayer methods on a Player instance in other code, you may need to proxy the relevant methods.

Python: RuntimeError: super-class __init__() of %S was never called

I tried to do some operation (setParent) on an object in Python (an instance of a class which inherits from a different class - to be specific, QtGui.QLabel), but during runtime the above error was raised. The object itself has had some fields with actual content (verified on debug), but from some reason I couldn't "use" it. What does the error mean and how can I fix it? For some additional information, I shall say that the object was returned from a static method before I tried to do this operation on it.
The subclass has a __init__() function of its own:
def __init__(self, image, father):
super(AtomicFactory.Image, self).__init__(father)
self.raw_attributes = image.attributes
self.attributes = {}
father.addChild(self)
self.update()
Now I wrote a similar code, a simple one, that had the same error on the line widget.setParent(mw) when the window was moved.
#!/usr/bin/env python
import sys
import copy
from PyQt4 import QtCore, QtGui
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_widget=QtGui.QWidget()
widget = QtGui.QPushButton('Test')
widget.resize(640, 480)
widget.setParent(main_widget)
widget.move(0, 0)
widget2=QtGui.QPushButton('Test2')
widget2.i=0
widget2.resize(600, 200)
widget2.setParent(main_widget)
widget2.move(640, 0)
def onResize(event):
print event
mw=copy.deepcopy(main_widget)
widget.setParent(mw)
widget2.setParent(mw)
widget.move(0, 0)
widget2.move(640, 480)
main_widget_width=main_widget.width()
widget_width=widget.width()
width2=main_widget_width-widget_width
height2=widget2.height()
widget2.resize(width2, height2)
widget2.move(640, 0)
main_widget.resizeEvent=onResize
def onClick():
size=(widget2.width(), widget2.height())
if(widget2.i%2==0):
widget2.resize(int(size[0]/2), int(size[1]/2))
else:
widget2.resize(size[0]*2, size[1]*2)
widget2.i+=1
QtCore.QObject.connect(widget, QtCore.SIGNAL('clicked()'), onClick)
main_widget.show()
sys.exit(app.exec_())
What was the problem?
If you want to inherit QObject (or QWidget), you must always call the super-class __init__:
class MyObject(QObject):
def __init__(self, *args, **kwargs):
super(MyObject, self).__init__(arguments to parent class)
#other stuff here
You may also call the parent's class's __init__ after some instructions, but you can't call QObject methods or use QObject attributes until you did so.
Edit:
In your case you are trying to deepcopy a QWidget, but this is not possible.
Python may be able to copy the wrapper of the QWidget, but the QWidget itself is a C++ object that python cannot handle with the default implementation of copy.deepcopy, hence whenever you call a method of the copied instance you get the RuntimeError because the underlying C++ object wasn't initialized properly.
The same is true for pickling these objects. Python is able to pickle the wrapper, not the C++ object itself, hence when unpickling the instance the result is a corrupted instance.
In order to support deepcopy() the QWidget class should implement the __deepcopy__ method, but it does not do that.
If you want to copy widgets you'll have to implement by yourself all the mechanism by hand.

PyQt4.QtCore.pyqtSignal object has no attribute 'connect'

I'm having issues with a custom signal in a class I made.
Relevant code:
self.parse_triggered = QtCore.pyqtSignal()
def parseFile(self):
self.emit(self.parse_triggered)
Both of those belong to the class: RefreshWidget.
In its parent class I have:
self.refreshWidget.parse_triggered.connect(self.tabWidget.giveTabsData())
When I try to run the program, I get the error:
AttributeError: 'PyQt4.QtCore.pyqtSignal' object has no attribute 'connect'
Help?
Thanks in advance.
I had the same exact problem as you.
Try moving
self.parse_triggered = QtCore.pyqtSignal()
out of your constructor but inside your class declaration. So instead of it looking like this:
class Worker(QtCore.QThread):
def __init__(self, parent = None):
super(Worker, self).__init__(parent)
self.parse_triggered = QtCore.pyqtSignal()
It should look like this:
class Worker(QtCore.QThread):
parse_triggered = QtCore.pyqtSignal()
def __init__(self, parent = None):
super(Worker, self).__init__(parent)
This might not be at all what you are looking for, but it worked for me. I switched back to old-style signals anyways because I haven't found a way in new-style signals to have an undefined number or type of parameters.
You also get that error message if you fail to call super() or QObject.__init__() in your custom class.
A checklist for defining custom signals in a class in Qt in Python:
your class derives from QObject (directly or indirectly)
your class __init__ calls super() (or calls QObject.__init__() directly.)
your signal is defined as a class variable, not an instance variable
the signature (formal arguments) of your signal matches the signature of any slot that you will connect to the signal e.g. () or (int) or (str) or ((int,), (str,))
I have recently started working with PySide (Nokia's own version of PyQt), and saw the exact same behaviour (and solution) with custom new-style signals. My biggest concern with the solution was that using a class variable to hold the signal would mess things up when I have multiple instances of that class (QThreads in my case).
From what I could see, QtCore.QObject.__init__(self) finds the Signal variable in the class and creates a copy of that Signal for the instance. I have no idea what QObject.__init__() does, but the resulting Signal does proper connect(), disconnect() and emit() methods (and also a __getitem__() method), whereas the class Signal or standalone Signal variables created outside of a QObject-derived class do not have these methods and can't be used properly.
To use the signal/slot system you need to have a QObject inherited class.
Here is a simple example:
from PySide import QtCore
class LivingBeing(QtCore.QObject):
bornSignal = QtCore.Signal() # initialise our signal
def __init__(self,name):
QtCore.QObject.__init__(self) # initialisation required for object inheritance
self.bornSignal.connect(self.helloWorld) # connect the born signal to the helloworld function
self.name = name #
self.alive = False
def summonFromClay(self):
self.alive = True
self.bornSignal.emit() # emit the signal
def helloWorld(self):
print "Hello World !, my name is %s, this place is so great !" % self.name
# now try the little piece of code
if __name__ == '__main__':
firstHuman = LivingBeing('Adam')
firstHuman.summonFromClay()
I had the same problem.
I forgot that if a class uses Signals, then it must inherit from QObject. I was doing some re-factoring and did not pay attention to this.
Why do you connect directly to the signal, while you can do
self.connect(widget, SIGNAL('parse_triggered()'), listener.listening_method)?
where self is, for example, the form itself, and may be the same as listener

Categories