Understanding Python Instance Variables in PyQt6 Classes

Why self matters when storing data in your GUI applications
Heads up! You've already completed this tutorial.

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:

python
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:

python
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:

python
def update_label(self):
    n = n + 1
    self.label.setText("%d" % n)

This will crash your program. There are two problems here:

  1. n doesn't exist yet. When Python sees n = n + 1, it tries to read the current value of n on the right-hand side before it can assign the result to n on the left. Since n was never defined, Python raises a NameError (or in a PyQt slot, the app may just appear to crash).

  2. Even if n did 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, n would 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__:

python
def __init__(self):
    super().__init__()
    self.n = 0  # Initialize the counter

Then use self.n in your method:

python
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.

python
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 with MainWindow(), not QWidget(). 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.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

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.

More info Get the book

Martin Fitzpatrick

Understanding Python Instance Variables in PyQt6 Classes was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.