I'm building my first PyQt app and running into problems. First, my window title wasn't changing, and then when I tried to store a counter variable inside a method (using
n = n + 1), my program crashes when I press a button. What am I doing wrong?
These are two of the most common stumbling blocks when you're getting started with Python GUI programming. Both come down to understanding how Python classes and objects work — specifically, how you create instances of your own classes, and how you store data on those instances using self. Let's walk through each issue and make sure you have a solid foundation to build on.
Creating an instance of your custom window class
When you define a custom window class in PyQt6, you need to actually use that class when creating your window. Here's a mistake that's easy to make:
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowTitle("Awesome App")
app = QApplication(sys.argv)
window = QWidget() # <-- This creates a plain QWidget, not your MainWindow!
window.show()
app.exec()
In this code, you've defined a MainWindow class with a custom title, but then you created a generic QWidget instead. Your MainWindow class is never used, so the title never gets set.
The fix is straightforward — create an instance of MainWindow:
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowTitle("Awesome App")
app = QApplication(sys.argv)
window = MainWindow() # Now we're using our custom class
window.show()
app.exec()
Now when the window appears, it will have the title "Awesome App" because MainWindow.__init__ runs when you create the instance, and that's where setWindowTitle is called. For a complete walkthrough of setting up your first PyQt6 window, see our Creating your first window with PyQt6 tutorial.
Local variables vs. instance variables
The second issue is a Python fundamental that trips up many people, especially those coming from other languages like C. Consider this method inside a class:
def update_label(self):
n = n + 1
self.label.setText("%d" % n)
This will crash your program. There are two problems here:
-
ndoesn't exist yet. When Python seesn = n + 1, it tries to read the current value ofnon the right-hand side before it can assign the result tonon the left. Sincenwas never defined, Python raises aNameError(or in a PyQt slot, the app may just appear to crash). -
Even if
ndid exist, it would be local. A variable defined inside a method only lives for the duration of that method call. The next time the method runs,nwould be gone and you'd hit the same problem again.
The solution is to store the variable on the object itself, using self. This makes it an instance variable — it belongs to the object and sticks around for as long as the object exists.
First, initialize the variable in __init__:
def __init__(self):
super().__init__()
self.n = 0 # Initialize the counter
Then use self.n in your method:
def update_label(self):
self.n = self.n + 1
self.label.setText("%d" % self.n)
Now self.n persists between method calls, and each button press increments the counter as expected.
A quick refresher on self
If you're coming from C or another language without classes like Python's, self can feel unfamiliar. Here's the short version: in Python, self refers to the specific object (instance) that a method is being called on. When you write self.n = 0, you're saying "store a value called n on this particular object." Any other method on the same object can then access self.n.
Think of self as the object's personal storage space. Anything you attach to self in one method is available in every other method on that same object. If you'd like a more thorough introduction to how classes work in Python, take a look at our Python Classes tutorial.
Complete working example
Here's a complete example that puts everything together. It creates a window with a label and a button. Each time you press the button, the counter increments and the label updates.
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
QApplication,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My Counter App")
# Initialize the counter as an instance variable
self.n = 0
# Create a label to display the count
self.label = QLabel("0")
self.label.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)
# Make the font a bit bigger
font = self.label.font()
font.setPointSize(25)
self.label.setFont(font)
# Create a button
self.button = QPushButton("Press me")
self.button.pressed.connect(self.update_label)
# Arrange widgets in a layout
layout = QVBoxLayout()
layout.addWidget(self.label)
layout.addWidget(self.button)
# QMainWindow needs a central widget to hold a layout
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def update_label(self):
self.n = self.n + 1
self.label.setText("%d" % self.n)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Copy this into a file, run it, and you should see a window with a large "0" and a button. Each press increments the number. The button's pressed signal is connected to our update_label method — to learn more about how signals and slots work, see Signals, Slots & Events in PyQt6.
Summary
Two things to remember as you start building PyQt6 apps:
-
Use your custom class. If you define
MainWindow(QMainWindow), make sure you create your window withMainWindow(), notQWidget(). Otherwise your custom__init__code never runs. -
Store persistent data with
self. Any variable you need to survive between method calls should be an instance variable (e.g.,self.n), initialized in__init__. Local variables inside a method disappear as soon as the method finishes.
These two concepts — creating the right class instance and using self for persistent state — will come up constantly as you build more complex applications. Once they click, everything else gets much smoother. When you're ready, explore PyQt6 Widgets to start building richer user interfaces.
Packaging Python Applications with PyInstaller by Martin Fitzpatrick
This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.