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())
CodePudding user response:
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.
CodePudding user response:
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.