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)
Advertisement
Answer
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.