Using python, pyside, I get this error:
self.setCheckState(value)
TypeError: could not convert 'BooleanEditor' to 'QCheckBox'
Beside that many of Google's result only show "TypeError: can not convert" instead of "could", I still have no idea how to fix this.
Code snippet:
class Editor(QGraphicsLayoutItem):
def __init__(self, name):
QGraphicsLayoutItem.__init__(self)
:
:
def update_value(self, value):
pass
class BooleanEditor(Editor, QCheckBox):
def __init__(self, parent, name, value, min, max):
Editor.__init__(self, name)
QCheckBox.__init__(self)
self.update_value(value)
def update_value(self, value):
self.old_value = value
self.setCheckState(value) # Error occurs here.
"value" that setCheckState receives will be Qt.CheckState. Upon running, the "value" is Qt.Unchecked (== 0) as expected, according to debug printing.
Notice that BooleanEditor employs multiple inheritance. I'm porting wxWidget app (that someone else made) to Qt, and for now I don't want to change this design because this comes from original (meaning mult inheritance itself here shouldn't be the cause since the original app works fine).
Environment) pyside 1.1.0, python 2.7.3, Ubuntu 12.04
Update-1) As #Luke Woodward suggests, I tried to swap the order of super classes as BooleanEditor(QCheckBox, Editor), then get a different error at different part.
class PaneGroup(GroupView, QFrame):
def __init__(self, parent, group, config, top = None):
GroupView.__init__(self, group, top)
QFrame.__init__(self)
:
sizer = QGraphicsGridLayout()
:
for param_descr in self.params:
name = param_descr['name']
type, val, min, max, description = param_descr['type'], config[name], param_descr['min'], param_descr['max'], param_descr['description']
try:
enum = eval(param_descr['edit_method'])['enum']
editor = self.top.EnumEditor(self, name, val, enum)
except:
editor = self.top._editor_types[type](self, name, val, min, max)
self.top.editors[name] = editor
sizer.addItem(editor, row, 1) # Error occurs here
Error:
TypeError: could not convert 'BooleanEditor' to 'QGraphicsLayoutItem'
Looks like an issue about initialization in multiple inheritance to me..
My very bad, it turned out I was using PyQt4 instead of PySide. When I use PySide, error doesn't occur (as #phihag's gist shows) whereas using PyQt4 yields the same result. It's actually curious but I won't investigate further now.
Use the following code for reproduction.
#!/usr/bin/env python
import sys
from PySide.QtCore import Qt
from PySide.QtGui import QApplication, QCheckBox, QGraphicsLayoutItem
#from PyQt4.QtCore import Qt
#from PyQt4.QtGui import QApplication, QCheckBox, QGraphicsLayoutItem
class Editor(QGraphicsLayoutItem):
def __init__(self, name):
QGraphicsLayoutItem.__init__(self)
def update_value(self, value):
pass
class BooleanEditor(Editor, QCheckBox):
def __init__(self, value):
Editor.__init__(self, "foo")
QCheckBox.__init__(self)
self.update_value(value)
def update_value(self, value):
self.old_value = value
self.setCheckState(value) # Error occurs here
print("End of update_value")
if __name__ == "__main__":
qApp = QApplication(sys.argv)
BooleanEditor(Qt.Checked)
PS. Why I didn't know I was using PyQt? For which one I use, I'm depending on this framework I've been working with (called qt_gui in ROS, Robot Operating System), which doesn't explicitly tells me which one in use (it's open source prj and documentation work is ongoing)...After hacking into its codes, I figured out default is PyQt.
Related
I'm having some issues when trying to execute a string/file from within a QPlainTextEdit, it appears to be some sort of scoping issues. What happens is that when the code EXECUTABLE_STRING is run from the global scope, it works fine. However, when it is run from a local scope, such as through the AbstractPythonCodeWidget, it either can't find the object to do inheritance TypeError: super(type, obj): obj must be an instance or subtype of type or runs into a name error NameError: name 'Test' is not defined. Which oddly changes based on whether or not the exec(EXECUTABLE_STRING) line is commented/uncommented when run. Any help would be greatly appreciated.
import sys
from PyQt5.QtWidgets import QApplication, QPlainTextEdit
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor
app = QApplication(sys.argv)
EXECUTABLE_STRING = """
from PyQt5.QtWidgets import QLabel, QApplication
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor
class Test(QLabel):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setText("Test")
a = Test()
a.show()
a.move(QCursor.pos())
"""
class AbstractPythonCodeWidget(QPlainTextEdit):
def __init__(self, parent=None):
super(AbstractPythonCodeWidget, self).__init__(parent)
self.setPlainText(EXECUTABLE_STRING)
def keyPressEvent(self, event):
if event.modifiers() == Qt.ControlModifier:
if event.key() == Qt.Key_Return:
# this does not work
#exec(compile(self.toPlainText(), "script", "exec"), globals(), locals())
exec(self.toPlainText())
return QPlainTextEdit.keyPressEvent(self, event)
w = AbstractPythonCodeWidget()
w.show()
w.move(QCursor.pos())
w.resize(512, 512)
# this works when run here, but not when run on the keypress event
# exec(EXECUTABLE_STRING)
sys.exit(app.exec_())
First of all, running exec based on user input can be a security issue, but most importantly usually leads to fatal crash unless lots of precautions are taken, since you're using the same interpreter for both your program and the user code: basically, if the code of the user fails, your program fails, but that's not the only problem.
The reason for which your code doesn't run properly is actually a bit complex, and it's related to the scope of the class name, which becomes a bit complex when running exec along with super().[1]
An interesting aspect is that if you remove the arguments of super (and you should, since Python 3), the program won't raise any error.
But that won't be enough: a is a local variable, and it will be garbage collected as soon as exec is finished, and since the label is assigned to that variable, it will be destroyed along with it.
A possible solution would be to make the reference persistent, for example by assigning it to self (since self exists in the scope of the executed script). This is a working example of the EXECUTABLE_STRING:
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QCursor
class Test(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.setText("Test")
self.a = Test()
self.a.show()
self.a.move(QCursor.pos())
As you can see, we don't need to import everything again anymore, we only import QLabel, since it wasn't imported in the main script, but everything else already exists in the scope of the script, including the current QApplication instance.
That said, all the above is only for knowledge purposes, as you should NOT use exec to run user code.
For instance, try to paste the following in the text edit, and run it:
self.document().setHtml('This should <b>NOT</b> happen!!!<br/><br/>Bye!')
self.setReadOnly(True)
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
QTimer.singleShot(2000, QApplication.quit)
As you can see, not only the above code is able to change the input, but also take complete control of the whole application.
You could prevent that by calling a function that would basically limit the scope of the execution:
def runCode(code):
try:
exec(code)
except Exception as e:
return e
class AbstractPythonCodeWidget(QPlainTextEdit):
# ...
def keyPressEvent(self, event):
if event.modifiers() == Qt.ControlModifier:
if event.key() == Qt.Key_Return:
error = runCode(self.toPlainText())
if error:
QMessageBox.critical(self, 'Script crash!', str(error))
return QPlainTextEdit.keyPressEvent(self, event)
But that's just because no self is involved: you could still use w.a = Test() with the example above.
So, if you want to run user made scripts in your program, exec is probably not an acceptable solution, unless you take enough precautions.
If you don't need direct interaction between your program and the user script, a possibility could be to use the subprocess module, and run the script with another python interpreter instance.
[1] If anybody has a valid resource/answer that might shed some light on the topic, please comment.
Found a similar issue that goes into more depth about how the scope works with Globals/Locals in exec here:
globals and locals in python exec()
Don't want to copy/paste the entire thread, but the answer that worked for me in this post was:
d = dict(locals(), **globals())
exec (code, d, d)
I have a decorator that is supposed to try / except any method given to the wrapper so that it can log on the GUI any errors encountered in order to give the user a chance to understand what is going on if the application is not working
The decorator is used on multiple methods of different classes and encapsulates slots and is therefore declared outside of the classes ( outside the only class in this simplified example)
Currently the following code does not work as there is never the right amount of arguments given ( always one too many)
I tried different ways of writing the decorator but my understanding of them is limited and it turned out into a guesswork, witch also means that even if I stumbled onto the solution by chance I would not know why it works and not all of the previous errors
Here is a simplified code that illustrates my issue, there are different spin boxes that connect to different slots each taking a different amount of arguments and the same decorator is used on all of them
import sys
from functools import partial
from PyQt5.QtWidgets import QSpinBox, QWidget, QHBoxLayout, QApplication
def except_hook(cls, exception, error_traceback):
sys.__excepthook__(cls, exception, error_traceback)
def my_decorator(method_reference):
def wrapper(*args, **kwargs):
try:
return method_reference(*args, **kwargs)
except Exception as e:
print("saved from " + str(e))
return wrapper
class Foo(QWidget):
def __init__(self):
super(Foo, self).__init__()
layout = QHBoxLayout()
sb1 = QSpinBox()
sb2 = QSpinBox()
sb3 = QSpinBox()
sb1.valueChanged.connect(partial(self.__pressed, sb1))
sb2.valueChanged.connect(partial(self.__pressed_two, sb2, "arg1"))
sb3.valueChanged.connect(partial(self.__pressed_three, sb3, "arg1", "arg2"))
layout.addWidget(sb1, 0)
layout.addWidget(sb2, 1)
layout.addWidget(sb3, 2)
self.setLayout(layout)
#my_decorator
def __pressed(self, spin_box):
print("spin box now has a value of " + str(spin_box.value))
#my_decorator
def __pressed_two(self, spin_box, arg1):
print("spin box now has a value of " + str(spin_box.value))
print("this is arg 1 : " + arg1)
#my_decorator
def __pressed_three(self, spin_box, arg1, arg2):
print("spin box now has a value of " + str(spin_box.value))
print("this is arg 1 : " + arg1)
print("this is arg 2 : " + arg2)
sys.excepthook = except_hook # by default QT will hide all errors behind a code, this is used to expose the issue
app = QApplication(sys.argv)
foo = Foo()
foo.show()
sys.exit(app.exec_())
Could it be possible to point out why my solution is not working? I did try to find examples and explanations and am successful in making decorators that would work for methods that are not used as pyqt slots but I cannot work this one out.
The issue doesn't seem to be with your decorator, but rather with connecting a method to the valueChanged signal. Since this signal emits data (the new value being set), you need to add an additional argument to the __pressed method that represent this data.
This is a simplified version of your code - you can see that the exception is properly handled by the decorator when the value in the spinbox gets too high.
import sys
from PyQt5.QtWidgets import QSpinBox, QWidget, QHBoxLayout, QApplication
def my_decorator(method_reference):
def wrapper(*args, **kwargs):
try:
return method_reference(*args, **kwargs)
except Exception as e:
print("saved from " + str(e))
return wrapper
class Foo(QWidget):
def __init__(self):
super(Foo, self).__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.spinbox = QSpinBox()
self.spinbox.valueChanged.connect(self.pressed)
layout.addWidget(self.spinbox)
#my_decorator
def pressed(self, new_value):
print(f"spin box now has a value of {new_value}")
if new_value > 5:
raise ValueError(f"Value {new_value} is too high!")
if __name__ == '__main__':
app = QApplication(sys.argv)
foo = Foo()
foo.show()
sys.exit(app.exec_())
Now, in my experience with PyQt, you don't always need to add the extra argument representing the data being sent by the signal. But in this case I'm assuming it is (somehow) mandatory due to the wrapping of the slot method. People more experienced with the library are free to correct me :-)
I'm using a wrapper object in my program and making it a Generic to detect incompatible uses for some function calls. The snippet below demonstrates how mypy can report an error if a bool wrapper is used as an argument to a function that takes an int wrapper.
CtrlType = TypeVar('CtrlType')
class Wrapper(Generic[CtrlType]):
def __init__(self, value: CtrlType):
self.value = value
def get(self) -> CtrlType:
return self.value
def squared(value: Wrapper[int]):
return value.get() * value.get()
not_an_int: Wrapper[bool] = Wrapper(False)
squared(not_an_int) # mypy reports incompatible-type error here
But this does not work with Qt widget classes
from PySide2.QtWidgets import QLineEdit, QCheckBox
def line_edit_only(value: Wrapper[QLineEdit]):
pass
line_edit_only(Wrapper(QCheckBox())) # no error reported here
line_edit_only(QCheckBox()) # no error for this either
line_edit_only(bool) # error is reported here though
Can anyone explain why mypy cannot detect the error for the wrong Generic type if it's derived from QWidget?
Well I was scripting a software designed via Python which I'll be using signals and slots too much in an PyQt5 application. I got an idea of creating a dictionary where all signals come in and each signal will have its own key in order to access (or basically to connect it to a function). The problem is that I get this error 'AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect' for some reason. I read about this error and found out that I have to declare the signals outside the constructor to get it working but unfortunately that will break my idea so that, I came here so somebody can solve my issue.
Here is the code if you still don't understand:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QRunnable, pyqtSlot, QThreadPool, QObject, pyqtSignal
class WorkerSignals(QObject):
signals = {}
def __init__(self, **kwargs):
QObject.__init__(self)
if (kwargs is not None):
for key, value in kwargs.items():
self.signals[key] = value
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
#pyqtSlot()
def run(self):
self.fn(*self.args, **self.kwargs)
And an example of creating signals:
worker_signals = WorkerSignals(result=pyqtSignal(str), error=pyqtSignal(str))
worker_signals.signals['result'].connect(self.on_receive_result)
worker_signals.signals['error'].connect(self.on_receive_error)
As indicated in the docs:
A signal (specifically an unbound signal) is a class attribute. When a
signal is referenced as an attribute of an instance of the class then
PyQt5 automatically binds the instance to the signal in order to
create a bound signal. [...]
So it is not only necessary that is it declared outside the constructor but it must be a static attribute since it serves as a prototype to create the signals that belong to the instance. A possible solution is to use type to create dynamic classes:
from PyQt5 import QtCore
d = {
"result": QtCore.pyqtSignal(str),
"error": QtCore.pyqtSignal(str)
}
WorkerSignals = type("WorkerSignals", (QtCore.QObject,), d)
if __name__ == '__main__':
import sys
app = QtCore.QCoreApplication(sys.argv)
worker_signals = WorkerSignals()
def on_result(text):
print("result:", text)
def on_error(text):
print("error:", text)
worker_signals.result.connect(on_result)
worker_signals.error.connect(on_error)
def emit_result():
worker_signals.result.emit(" 1+1=2 ")
def emit_error():
worker_signals.error.emit(" :( ")
QtCore.QTimer.singleShot(1000, emit_result)
QtCore.QTimer.singleShot(2000, emit_error)
QtCore.QTimer.singleShot(3000, app.quit)
sys.exit(app.exec_())
The full traceback for my error is:
> python zthreadtest_tjedit.py
None
Traceback (most recent call last):
File "zthreadtest_tjedit.py", line 17, in run self.function()
TypeError: 'list' object is not callable
I apologize that's this code is a bit system specific and probably won't serve as an executable example for most people. Hopefully the solution is simple enough for someone to see right off. If you're not running zfs with a currently imported zpool, but are on a *nix platform with the weir.zfs module installed, it will return an empty list with the non-threaded code enabled (see comments in code for toggling threading). With the threading module enabled, it throws the error as shown above.
Confusing to me is that the second snip of code from Jo Plaete (https://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/) runs without error, and I simply modified this code to my needs.
EDIT: This error-causing difference may be related to the list object used in my code and not hers, but I still need to make mine work.
My question is: how do I resolve my error such that my threaded module runs correctly?
This seems simple, but I'm absolutely stumped. So much so that this is the first question I've posted on any help forum ever! I hope I've asked my question properly and I appreciate any help.
My Code, from much larger pyside gui program:
import PySide, sys
from PySide import QtCore, QtGui
from PySide.QtCore import *
from PySide.QtGui import *
import re, subprocess, threading
from weir import zfs
class WorkerThread(QThread):
def __init__(self, function):
QThread.__init__(self)
self.function = function
def __del__(self):
self.wait()
def run(self):
self.function()
return
class MainZ(QMainWindow):
def __init__(self):
super(MainZ, self).__init__()
# print(self)
# imported_pools = self.get_imported() # No threading
imported_pools = self.thread_test() # Use threading module
print(imported_pools)
def thread_test(self):
self.threader = WorkerThread(self.get_imported())
self.threader.start()
def get_imported(self):
pool_string = subprocess.getoutput(
'zpool list |grep -v ^NAME.*SIZE.*ALLOC |grep -o ^[a-Z0-9]*')
imported_pools = re.split(r'\s *', pool_string)
return imported_pools
app = QApplication(sys.argv)
form = MainZ()
app.exec_()
Code I modeled from Jo Plaete that works for me without error:
import sys, time
from PySide import QtCore, QtGui
class GenericThread(QtCore.QThread):
def __init__(self, function, *args, **kwargs):
QtCore.QThread.__init__(self)
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self):
self.function(*self.args,**self.kwargs)
return
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
""" Add item to list widget """
print("Add: " + text)
self.listwidget.addItem(text)
self.listwidget.sortItems()
def addBatch(self,text="test",iters= 5,delay=0.2):
""" Add several items to list widget """
for i in range(iters):
time.sleep(delay) # artificial time delay
self.add(text+" "+str(i))
def test(self):
self.listwidget.clear()
self.genericThread = GenericThread(
self.addBatch,"from generic thread ",delay=0.3)
self.genericThread.start()
# run
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
app.exec_()
The
self.threader = WorkerThread(self.get_imported())
should read
self.threader = WorkerThread(self.get_imported)
When creating the thread, you want to pass the function itself and not the result of calling the function (which is a list).