Understanding a piece of python code - python

My question refers to the accepted answer of the question How to capture output of Python's interpreter and show in a Text widget? which shows how to redirect standard output to a QTextEdit.
The author, Ferdinand Beyer, defines a class EmittingStream as such:
from PyQt4 import QtCore
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
He uses the class like this:
# Within your main window class...
def __init__(self, parent=None, **kwargs):
# ...
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
cursor = self.textEdit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.textEdit.setTextCursor(cursor)
self.textEdit.ensureCursorVisible()
I don't understand the line that instantiates the EmittingStream class. It looks as if the keyword argument textWritten=self.normalOutputWritten connects the textWritten-signal to the normalOutputWritten-slot, but I do not understand why this works.

This feature is documented here:
It is also possible to connect signals by passing a slot as a keyword
argument corresponding to the name of the signal when creating an
object, or using the pyqtConfigure() method of QObject. For example
the following three fragments are equivalent:
act = QtGui.QAction("Action", self)
act.triggered.connect(self.on_triggered)
act = QtGui.QAction("Action", self, triggered=self.on_triggered)
act = QtGui.QAction("Action", self)
act.pyqtConfigure(triggered=self.on_triggered)

Related

Unable to overwrite class method in pyqt6 application

So normally you can overwrite a class method by doing something like this.
class A():
def test(self):
return 1+1
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
a=A()
a.test = edit_patch(a.test)
Now a.test will return 'this test worked' instead of 2. I'm trying to do something similar in my pyqt6 application. The function below belongs to the "main" class in my code and is connected to a button click. This function is meant to instantiate another class (which is another window in pyqt6). That part works, but I would like to alter the behavior of the select function in this instance. However the method above doesn't seem to work as the select function continues to exhibit the default behavior.
def edit_proj(self):
self.psearch=PSearch(conn=self.conn,parent=self)
self.psearch.select = edit_patch(self.psearch.select)
self.psearch.show()
Any help on this would be great
As requested, here is an MRE
from PyQt6 import QtCore, QtGui, QtWidgets
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(50, 50)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.EditProjButton = QtWidgets.QPushButton(self.centralwidget)
self.EditProjButton.setObjectName("EditProjButton")
self.EditProjButton.clicked.connect(self.nextwindow)
def nextwindow(self):
print('hello from main window')
self.newwindow=Ui_ProjSearchForm(QtWidgets.QWidget())
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.show()
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def setupUi(self, ProjSearchForm):
ProjSearchForm.setObjectName("ProjSearchForm")
ProjSearchForm.resize(100, 100)
self.gridLayout = QtWidgets.QGridLayout(ProjSearchForm)
self.gridLayout.setObjectName("gridLayout")
self.SearchButton = QtWidgets.QPushButton(ProjSearchForm)
self.SearchButton.setObjectName("SearchButton")
self.gridLayout.addWidget(self.SearchButton, 0, 2, 1, 1)
def select(self):
print('this is default behavior')
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())
Signal connections work by passing a reference to a callable, and that reference is an "internal" pointer to that function. Overwriting the name of that function will have absolutely no result.
Take this example:
class Test(QPushButton):
def __init__(self):
super().__init__('Click me!')
self.clicked.connect(self.doSomething)
self.doSomething = lambda: print('bye!')
def doSomething(self):
print('hello!')
The code above will always print "hello!", because you passed the reference to the instance method doSomething that existed at the time of the connection; overwriting it will not change the result.
If you need to create a connection that can be overwritten, you have different possibilities.
Pass the function to the constructor
You can set the function as an optional argument in the __init__ and then connect it if specified, otherwise use the default behavior:
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
if func is not None:
self.SearchButton.clicked.connect(func)
else:
self.SearchButton.clicked.connect(self.select)
Create a method for the connection
In this case we pass the reference to a specific method that will create the connection, eventually disconnecting any previous connection (remember that signals can be connected to multiple functions, and even the same function multiple times). This is similar to the approach above.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.setSelectFunc(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def select(self):
print('this is default behavior')
def setSelectFunc(self, func):
try:
self.SearchButton.clicked.disconnect(self.select)
except TypeError:
pass
self.select = func
self.SearchButton.clicked.connect(self.select)
Use a lambda
As said above, the problem was in trying to overwrite the function that was connected to the signal: even if the connected function is a wrapper, the direct reference for the connection is not actually overwritten.
If you, instead, connect to a lambda that finally calls the instance method, it will work as expected, because the lambda is dynamically computed at the time of its execution and at that time self.select will be a reference to the overwritten function.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.select = edit_patch(self.newwindow.select)
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(lambda: self.select())
def select(self):
print('this is default behavior')
Some unrelated but still important notes:
You should never edit pyuic generated files, nor try to merge their code into your script or mimic their behavior. Instead, follow the official guidelines about using Designer.
Passing a new QWidget instance as argument is pointless (other than wrong and potentially dangerous); if you want to create a new window for the new widget, just avoid any parent at all, otherwise use QDialog for modal windows.
Only classes and constants should have capitalized names, everything else should be named starting with lowercase letters (this includes object names created in Designer); read more about this and other important topics in the official Style Guide for Python Code.
Ok I think I've figured out a solution (in the MRE posted in question). There's some shenanigans that go on in the back ground once you connect a button in the UI to a function. It's not a "live" connection like in the a.test example, so edits to the function later don't have an impact on how the button functions.
So, if we replace
self.newwindow.select = edit_patch(self.newwindow.select)
with
self.newwindow.SearchButton.clicked.disconnect()
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.SearchButton.clicked.connect(self.newwindow.select)
we suddenly get the desired behavoir from the button. This was entirely too frustrating.

How to execute a class method from another class with the method passed a parameter in Python

I'm a beginner in learning python..
I'm looking for help in solving an OOP problem
My main program has something simplified like below:
class abc(Frame):
def _init_(self, master)
Frame.__init__(self)
self.B1 = Mybutton(self.master, self.cmd)
def cmd(self):
print("hello world")
In the main program, I import Mybutton class in another file, which is simplified as below:
class Mybutton():
def _init_(self, parent, command):
self.command = command
def A_ramdom_fcn(self):
...
self.command() ------------------>> here I want to execute the command
in class abc, not in class Mybutton.
How to execute a method from another class that is passed as an instance method, you may ask why not just execute it in class abc, but I have event attached to button press, it needs to do a roundabout to achieve this..
First, fix the typos: missing : in abc's init method, and it should be __init__ (with two underscores) for both classes.
It seems like you've gotten yourself turned around. You've set things up correctly using composition: an abc has a Mybutton, and it looks like you correctly pass the function to Mybutton so that it can execute it. In fact, your code will work as written if you do, for example
a = abc(master) # no context for what master is, but I assume you have it
a.B1.A_ramdom_fcn()
With the way you've set things up, you don't want to import and make instances of Mybutton in your main program (what abc would they belong to?). You want to import and make instances of abc. You then access their internal Mybutton like I've shown in the example above. This works because when you pass self.cmd to the Mybutton constructor while inside the abc constructor, it's already a bound method of the abc you're constructing.
As an addendum, it looks like you might be having an XY problem with regards to why you need such a roundabout method. Is there any reason why you can't simply pass abc.cmd to the button press handler?
Theoretically, what you are trying is possible, you can capture the object method into variable and call it later (python 3):
class Window:
def __init__(self):
self.my_button = Mybutton(self.cmd)
def cmd(self):
print("hello world")
class Mybutton:
def __init__(self, command):
self.command = command
def a_ramdom_fcn(self):
self.command.__call__()
win = Window()
win.my_button.a_ramdom_fcn()
I assume you are trying to make the generic Button class which doesn't know what to do when it's clicked and you want to put the actual logic into your Window class.
That makes sense, but it would be even better to extract the logic into the third, Command class. This allows us to limit the Window responsibility and also avoid the trick with method-as-variable (the command we pass to the button object is just another object):
class HelloWorldCommand:
def execute(self):
print("Hello world")
class Window:
def __init__(self):
self.my_button = Mybutton(
HelloWorldCommand()
)
class Mybutton:
def __init__(self, command):
self.command = command
def a_ramdom_fcn(self):
self.command.execute()
win = Window()
win.my_button.a_ramdom_fcn()

Progress bar with PyQt4

I have written the following code that parses a text file, breaks it into tokens and inserts these tokens into the database. I want to show the current status of the process using the progress bar but the following code isn't working.
I wrote the following code based on this How to connect pyqtSignal between classes in PyQT
yast_gui.py
class YastGui(QtGui.QMainWindow):
incrementTokenSignal = QtCore.pyqtSignal(int)
...
def __init__(self):
self.incrementTokenSignal.connect(self.increment_token_count)
...
def increment_token_count(self, val):
msg = "{}/{}".format(val, self.total_db_records)
self.ui.records_processed_value_label.setText(msg)
yast.py
class LogFile(object):
def __init__(self, file_path, YastGui_object):
super(LogFile, self).__init__()
# Gui object
self.gui = YastGui_object
self.total_db_records = 0
...
def tokenize(self):
for i, record in enumerate(myfile):
...
self.gui.incrementFilterSignal.emit(i + 1)
settings.session.commit()
According to this PYQT and progress Bar during Long Process, I should create QTheads to deal with the progress bar but I'm not sure on how to do it.
Here is the entire Gui file and main file.
I found the solution to my problem here Change the value of the progress bar from a class other than my GUI class PyQt4.
The trick is to pass the progressBar object to the function as a parameter and then use progressBar.setValue() inside that function.

How to inherit from GObject class?

I want to write application with a tree view widget in witch I will store my objects of class 'Item'.
I know that in order to do so my 'Item' class must inherit from GObject class. Unfortunately something is wrong and I don't see text of items on my tree. I only get this warning:
Warning: unable to set property 'text' of type 'gchararray' from value of type '__main__+Item'
What I will have to do to fix this?
This sample program is demonstrates problem and it's ready to test fixes:
#!/usr/bin/env python3
from gi.repository import Gtk
from gi.repository import GObject
class Item(GObject.GObject):
text = GObject.property(type=str, default='item', flags=GObject.PARAM_READWRITE)
def __init__(self, title):
GObject.GObject.__init__(self)
self.__title = title
def __str__(self):
return self.__title
GObject.type_register(Item)
class MainWindow(Gtk.Window):
def __init__(self):
super().__init__(Gtk.WindowType.TOPLEVEL)
self.connect('destroy', self.on_destroy)
tree_model = Gtk.TreeStore(Item.__gtype__)
# tree_model = Gtk.TreeStore(str)
text_renderer = Gtk.CellRendererText()
text_column = Gtk.TreeViewColumn(None, text_renderer)
text_column.add_attribute(text_renderer, 'text', 0)
tree_view = Gtk.TreeView(tree_model)
tree_view.append_column(text_column)
self.add(tree_view)
self.show_all()
tree_model.append(None, (Item('test'),))
# tree_model.append(None, ('It works!',))
def on_destroy(self, e):
Gtk.main_quit()
if __name__ == '__main__':
MainWindow()
Gtk.main()
GtkCellRendererText requires string (gchararray) data for its text property, and it is receiving custom GObject values. The __str__ function works on the Python level and is never invoked by GObject.
Fortunately, what you want to achieve doesn't require subclassing GObject. You need to do the following:
Specify the tree store column as GObject.TYPE_PYOBJECT. This will allow you to append your instances to the tree store without inheritance from GObject or special properties.
Use set_cell_data_func on the tree view columns to extract textual data from your instances stored in the model.
See this answer for a working example of this technique.

Translation using PySide outside the constructor

I am using pySide and try to internationalize my project. The problem is that none of the Strings get translated if I do not translate them through the constructor.
I would like to translate the Strings in other methods as well. What could be the problem?
Here is my module:
class InterpolationView(QWidget):
def __init__(self, parent=None, flags=Qt.Widget):
super(InterpolationView, self).__init__(parent, flags)
load_ui('ui/wizards/interpolation/interpolation_view.ui', self)
self.stepGroupBox.setTitle(Dictionary.map_interpolation_view["step_planning"])
I translate my Strings from the injected .ui file in the init method. The translations come from a module called Dictionary that includes all the necessary translations:
class Dictionary(object):
map_interpolation_view = None
def __init__(self):
super(Dictionary, self).__init__()
self.initialize_dictionary()
def initialize_dictionary(self):
Dictionary.map_interpolation_view = dict()
Dictionary.map_interpolation_view["step_planning"] = QApplication.translate("Dictionary", "Step Planning")
The Dictionary is initialized in the MyPlugin module which is a python plugin.This is where I also initialize my translator.
class MyPlugin(Plugin):
def __init__(self, context):
super(MyPlugin, self).__init__(context)
translate_to_german = True
if(translate_to_german):
translator = load_translation('de')
application = QCoreApplication.instance()
application.installTranslator(translator)
self.__dictionary = Dictionary()
I would like to get rid of this special Dictionary module and just translate the Strings like this:
class InterpolationView(QWidget):
def __init__(self, parent=None, flags=Qt.Widget):
super(InterpolationView, self).__init__(parent, flags)
load_ui('ui/wizards/interpolation/interpolation_view.ui', self)
def translate(self):
self.stepGroupBox.setTitle(QApplication.translate("InterpolationView", "Step Planning"))
and be able to call the translate method from any other class. This does not work however, the Strings are not translated then. Why?

Categories

Resources