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.
Related
I am writing an application that uses a Qt GUI and am now trying to multithread it (first time learner). A longer task will be contained within the worker thread eventually but output logs will be required to be written as it goes. The GUI class has a method to output these logs to a plain text widget. Up until now, everything has been running within the GUI class.
I currently have the following code (included the important bits for brevity):
class Worker(QRunnable):
#pyqtSlot()
def run(self):
Ui.logentry(self, "Test")
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi(resourcepath('./pycdra.ui'), self)
self.outputlog = self.findChild(QtWidgets.QPlainTextEdit, 'outputlog')
self.button = self.findChild(QtWidgets.QPushButton, 'button')
self.button.clicked.connect(self.Button)
self.threadpool = QThreadPool()
self.logentry("Available threads: %d" % self.threadpool.maxThreadCount())
def Button(self):
worker = Worker()
self.threadpool.start(worker)
def logentry(self, returntext):
self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext))
self.outputlog.repaint()
def timestamp(self):
import datetime
ts = datetime.datetime.now(tz=None).strftime("%Y-%m-%d %H:%M:%S"))
return ts
def applic():
app = QApplication(sys.argv)
window = Ui()
window.show()
sys.exit(app.exec_())
applic()
When I try running this, the GUI loads perfectly, but upon pushing button the Ui.logentry part of Worker returns the error: AttributeError: 'Worker' object has no attribute 'outputlog'
I attempted making logentry() and timestamp() global so they can access and be accessed by both classes, but the closest I got with the line Ui.self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext)) was that 'Ui' object has no attribute 'self'.
What am I missing? Am I right in making them global or are there other means to do this?
There are various problems with your code. I'll try to address them in order of importance.
logentry is an instance method of Ui, which means that it requires an instance of Ui in order to be run; what you're trying to achieve would never work since that self refers to an instance of Worker, which clearly doesn't have any outputlog attribute (which is what logentry expects instead). Remember: the self argument is not there just for fun, it always refers to the object (the instance, for instance methods) the function is called from; self.logentry means "call the function logentry using self as first argument. Since, in your case, self refers to an instance of Ui, that explains your main issue, since that instance doesn't have that attribute.
None is exactly what its name says: nothing. The timestamp function will throw another AttributeError, since there's no strftime attribute in None. That is part of a datetime object, so you have to get a now object, then call its strftime function against it. In any case, the tz argument requires a tzinfo subclass.
pyqtSlot only works for Qt classes that inherit from QObject (such as QWidget). QRunnable isn't one of those classes. You can check the whole inheritance tree in the header of the documentation in each class: if there's a "Inherits:" field, go up until there's none. If you get a QObject inheritance at some point, then you can use slots and signals for that object, otherwise not. Consider that: QWidget also inherits from QObject; all Qt widgets inherit from QWidget; there are Qt classes that inherit from QObject but are not widgets.
Even ignoring all the above, access to UI elements is always forbidden from external threads (including QRunnable objects); while you can theoretically (but unreliably) get their properties, trying to set them will most likely cause a crash; in order to change something in a UI element, using signals is mandatory. Note that "access" also includes creation, and always results in a crash.
Calling repaint is a common (and wrong) attempt to solve the above issue; that's unnecessary, as properly setting widget properties (like using appendPlainText()) already results in a scheduled repaint on its own; there are very few cases for which repaint is actually necessary, and the rule of thumb is that if you're calling it you probably don't know what your doing or why your doing it. In any case, calling update() is always preferred, and it must always be called from the main UI thread anyway.
Using imports in a function is rarely required, as import statements should always be in the very beginning of the script; while there are cases for which imports can (or should) be done later or in a specific function, doing them in a function that is likely to be called often, makes using them there completely pointless. Also, datetime is part of the standard library, so importing it on demand will hardly affect performance (especially considering its "performance weight" against what a big library like Qt is compared to it).
When the ui is loaded from a .ui file (or a pyuic generated file), PyQt alread creates all widgets as instance attributes, so there's no need for findChild. Unfortunately there are a lot of tutorials that suggest that approach, and they are just completely and plain wrong. You can already access those widgets as self.outputlog and self.button right after uic.loadUi.
Function names (like variables and attributes) should always begin with a lower case letter, as only classes and constants should begin with upper cases (see the official Style Guide for Python Code). Also, object names should always explain what those object do (see "self-documenting code"): a function that does an "action" should have a verb; if it's named "Button" it doesn't tell me that it's going to do some processing, and that's not a very good thing.
A "main" function (like your applic) usually makes sense within the common if __name__ == '__main__': block, which ensures that that function doesn't get called in case the file gets imported instead of being directly run. (See this answer and the related question).
Since QRunnable doesn't inherit from QObject, we can create a QObject subclass that acts as a signal "proxy", and make it a member of the QRunnable instance. Then we must connect to that signal everytime we create a new Worker object.
Here is a revised version of your code, based on the above points.
import datetime
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUi
class WorkerSignals(QObject):
mySignal = pyqtSignal(str)
class Worker(QRunnable):
def __init__(self):
super().__init__()
self.signalProxy = WorkerSignals()
self.mySignal = self.signalProxy.mySignal
def run(self):
self.mySignal.emit("Test")
class Ui(QMainWindow):
def __init__(self):
super(Ui, self).__init__()
loadUi('./pycdra.ui', self)
self.button.clicked.connect(self.startWorker)
self.threadpool = QThreadPool()
self.logentry("Available threads: %d" % self.threadpool.maxThreadCount())
def startWorker(self):
worker = Worker()
worker.mySignal.connect(self.logentry)
self.threadpool.start(worker)
def logentry(self, returntext):
self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext))
def timestamp(self):
ts = datetime.datetime.now()
return ts.strftime("%Y-%m-%d %H:%M:%S")
def applic():
import sys
app = QApplication(sys.argv)
window = Ui()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
applic()
I suggest you to carefully study the provided code and the differences with yours, and then do some careful, patient research both on the links given above and the following topics:
classes and instances, and the meaning of self;
python types (including None);
what is event driven programming and how it relates with graphical interfaces;
the most important Qt classes, QObject and QWidget, and all their properties and functions (yes, they are a lot);
general PyQt related topics
(most importantly, signals/slots and properties);
code styling and good practices;
Use the class' initialised form
class foo:pass
selfuotsideclass=foo()
Use variable selfoutsideclass as self
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.
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.
I've used PyQt4 quite a lot - sometimes I like to overload some of the objects to allow me to add some functionality. This works fine in PyQt4, eg:
from PyQt4 import QtGui
button = QtGui.QPushButton()
class MyPushButton(QtGui.QPushButton): pass
button.__class__ = MyPushButton
However, I'm trying to adapt some of my code so that it uses PySide instead of PyQt4. My understanding is that they should have the same functionality. PySide will not allow me to do the same thing.
from PySide import QtGui
button = QtGui.QPushButton()
class MyPushButton(QtGui.QPushButton): pass
button.__class__ = MyPushButton
This errors with:
TypeError: __class__ assignment: only for heap types
Is there another way I can change the class of my object to avoid this error? I'm not really sure what's causing it.
NOTE: I need to change the class of the object after it is created as the object is created in a library which is compiled by pyuic. Also my PySide installation does not have the uic module.
PySide uses Shiboken to generate the CPython bindings for Qt from
its C++ classes. All of the Qt python classes such as QPushButton are
implemented in C++ which is why you cannot overwrite __class__.
>>> button = QPushButton()
>>> button.__class__ = MyButton
TypeError: __class__ assignment: only for heap types
According to the Shiboken's documentation, you can monkey patch (or duck punch)
the methods on an already instantiated object so long as the methods are
virtual (overridable):
import types
def override_text(self):
return 'overridden'
# Bind override_text() to button.
button.text = types.MethodType(override_text, button, QPushButton)
Taking this further you can sub-class QPushButton as MyButton, and
dynamically inject the methods from MyButton into the QPushButton
instance. Making MyButton a sub-class of QPushButton is purely
optional, but would allow you to make your own instances of MyButton
in addition to the modified QPushButton instances.
Let's define MyButton as a sub-class of QPushButton.
class MyButton(QPushButton):
def text(self):
# This will override QPushButton's text() method.
print("inside MyButton.text()")
return QPushButton.text(self)
NOTE: You have to use the old-style of calling parent class methods.
super() fails with a TypeError because self is actually a QPushButton
and not a MyButton when the method is injected.
Or if you wanted to take more of a mixin approach, let's define MyButtonOverrides:
class MyButtonOverrides(object):
def text(self):
# This will override QPushButton's text() method.
print("inside MyButtonOverrides.text()")
return self.__class__.text(self)
NOTE: You can call the QPushButton.text() directly through self.__class__
because you won't be using MyButtonOverrides directly.
Now let's define extend_instance() which will inject your override
methods from MyButton (or MyButtonOverrides) into the QPushButton
instance:
import inspect
def extend_instance(obj, cls):
for name, attr in vars(cls).items():
if inspect.isroutine(attr):
# Bind instance, class and static methods to *obj*.
setattr(obj, name, attr.__get__(obj, obj.__class__))
If you'd like your class methods to remain bound to their originating class
(e.g., MyButton) then use the following:
def extend_instance(obj, cls):
for name, attr in vars(cls).items():
if inspect.isroutine(attr):
if isinstance(attr, classmethod):
# Bind class methods to *cls*.
setattr(obj, name, attr.__get__(cls, cls))
else:
# Bind instance and static methods to *obj*.
setattr(obj, name, attr.__get__(obj, obj.__class__))
Through my testing this works in Python 2.7 and 3.3, but should
work on 2.6+ and 3+.
Finally to modify the button, use extend_instance().
>>> button = QPushButton()
>>> extend_instance(button, MyButton)
>>> button.text()
inside MyButton.text()
u''
Monkey-patching __class__ is completely unnecessary. Instead, you should promote the relevant widgets in Qt Designer so that they automatically use your subclass.
To do this, in Qt Designer, right-click the widget and select "Promote to...". In the dialog, set "Promoted class name" to your subclass (e.g. "MyPushButton"), and set "Header file" to the python import path for the module containing the subclass (e.g. "myapp", or "myapp.gui", or whatever).
Now click "Add", and then "Promote", and you will see the class change from "QPushButton" to "MyPushButton" in the Object Inspector pane.
When you re-generate your ui module with pyuic or pyside-uic, it will contain code like this:
class Ui_Window(object):
def setupUi(self, Window):
...
self.button = MyPushButton(Window)
self.button.setObjectName("button")
...
from myapp import MyPushButton
So the promoted button will be created as an instance of MyPushButton, and there is no longer any need to hack __class__.
This technique of promoting widgets will work for both PyQt and PySide.
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