Updating PyQt6 Widgets from Functions and Classes

How to get data back into your labels and widgets instead of just printing to the console
Heads up! You've already completed this tutorial.

When you're starting out with PyQt6, one of the first hurdles you'll run into is figuring out how to get data back into your widgets. Printing values to the console is straightforward, but at some point you want to click a button and see the result appear in a QLabel or another widget in your actual application window.

This tutorial walks through how to structure a PyQt6 application so that your data-generating code (like a random name generator) can feed its results into the GUI. We'll start simple and gradually improve the structure, so you'll end up with clean, readable code that's easy to maintain — even as your app grows.

The Starting Point: A Simple Generator

Let's say you have a class that generates random data. For this tutorial, we'll keep it simple with a random name generator:

python
import random


class NPCGen:
    """Generates random NPC attributes."""

    FIRST_NAMES = ["Kira", "Dash", "Orla", "Bren", "Vex", "Zara"]
    LAST_NAMES = ["Solaris", "Voidwalker", "Ashborne", "Duskwood", "Starfall"]

    def generate_name(self):
        first = random.choice(self.FIRST_NAMES)
        last = random.choice(self.LAST_NAMES)
        return f"{first} {last}"

    def generate_hitpoints(self):
        return random.randint(10, 100)

This class has nothing to do with the GUI — it just produces data. That's a good thing. Keeping your data logic separate from your display logic makes your code easier to understand and maintain.

Displaying a Value in a QLabel

The core concept is simple: to update a widget, you need a reference to it. When you create a QLabel inside your window class and store it on self, you can access it later from any method in that class.

Here's a minimal example that creates a label and a button. Clicking the button generates a random name and displays it in the label:

python
import sys
import random

from PyQt6.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton, QVBoxLayout,
)


class NPCGen:
    """Generates random NPC attributes."""

    FIRST_NAMES = ["Kira", "Dash", "Orla", "Bren", "Vex", "Zara"]
    LAST_NAMES = ["Solaris", "Voidwalker", "Ashborne", "Duskwood", "Starfall"]

    def generate_name(self):
        first = random.choice(self.FIRST_NAMES)
        last = random.choice(self.LAST_NAMES)
        return f"{first} {last}"


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("NPC Generator")

        self.npc_gen = NPCGen()

        layout = QVBoxLayout()

        self.name_label = QLabel("Click Generate to create an NPC")
        layout.addWidget(self.name_label)

        generate_button = QPushButton("Generate")
        generate_button.clicked.connect(self.generate_npc)
        layout.addWidget(generate_button)

        self.setLayout(layout)

    def generate_npc(self):
        name = self.npc_gen.generate_name()
        self.name_label.setText(name)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Run this, click "Generate," and you'll see a new random name appear in the label each time. The flow is:

  1. The button's clicked signal calls self.generate_npc.
  2. generate_npc asks the NPCGen instance for a new name.
  3. The returned name is passed directly to self.name_label.setText(...).

Because we stored the label as self.name_label, we can reach it from any method in the class. That's the whole trick.

Using Layouts Instead of Fixed Positions

You might be tempted to position widgets using .move() and .adjustSize(). This works for quick experiments, but it gets painful fast — every time you add or remove a widget, you have to manually recalculate positions.

Qt's layout managers handle all of that for you. QVBoxLayout stacks widgets vertically, QHBoxLayout arranges them horizontally, and QGridLayout places them in a grid. You can also nest layouts inside each other for more complex arrangements.

In the example above, we used QVBoxLayout, which simply stacks the label on top of the button. If you later add more labels, just call layout.addWidget(...) for each one and they'll be neatly arranged automatically.

Scaling Up: Multiple Labels

A single label is easy enough. But what happens when you have 15 or 20 labels — one for the NPC's name, another for hit points, another for a home planet, and so on? Storing each one as a separate self.something_label attribute starts to feel repetitive, and updating them all in a button handler gets long and messy.

A cleaner approach is to use a dictionary that maps each stat name to its corresponding label widget. Then, when you generate new values, you can loop through the dictionary and update everything in one go.

Let's expand our example to include several NPC stats:

python
import sys
import random

from PyQt6.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton,
    QVBoxLayout, QHBoxLayout,
)
from PyQt6.QtGui import QFont


class NPCGen:
    """Generates random NPC attributes."""

    FIRST_NAMES = ["Kira", "Dash", "Orla", "Bren", "Vex", "Zara"]
    LAST_NAMES = ["Solaris", "Voidwalker", "Ashborne", "Duskwood", "Starfall"]
    SPECIES = ["Human", "Twi'lek", "Wookiee", "Rodian", "Zabrak"]
    HOMEWORLDS = ["Coruscant", "Tatooine", "Naboo", "Kashyyyk", "Corellia"]

    def generate_name(self):
        first = random.choice(self.FIRST_NAMES)
        last = random.choice(self.LAST_NAMES)
        return f"{first} {last}"

    def generate_hitpoints(self):
        return random.randint(10, 100)

    def generate_species(self):
        return random.choice(self.SPECIES)

    def generate_homeworld(self):
        return random.choice(self.HOMEWORLDS)

    def generate_all(self):
        """Generate a complete set of NPC stats as a dictionary."""
        return {
            "Name": self.generate_name(),
            "Species": self.generate_species(),
            "Homeworld": self.generate_homeworld(),
            "Hit Points": str(self.generate_hitpoints()),
        }


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("NPC Generator")
        self.setMinimumWidth(300)

        self.npc_gen = NPCGen()

        main_layout = QVBoxLayout()

        # Build a label for each stat and store them in a dictionary.
        self.stat_widgets = {}
        for stat_name in ["Name", "Species", "Homeworld", "Hit Points"]:
            row_layout = QHBoxLayout()

            label = QLabel(f"{stat_name}:")
            label.setFont(QFont("Arial", 10, QFont.Bold))
            label.setFixedWidth(100)
            row_layout.addWidget(label)

            value_label = QLabel("—")
            row_layout.addWidget(value_label)

            main_layout.addLayout(row_layout)

            # Map the stat name to the value label so we can update it later.
            self.stat_widgets[stat_name] = value_label

        generate_button = QPushButton("Generate")
        generate_button.clicked.connect(self.generate_npc)
        main_layout.addWidget(generate_button)

        self.setLayout(main_layout)

    def generate_npc(self):
        stats = self.npc_gen.generate_all()
        for stat_name, value in stats.items():
            self.stat_widgets[stat_name].setText(value)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

NPC Generator window with random stats displayed

Here's what's happening:

  • NPCGen.generate_all() returns a dictionary of all the NPC's stats. Each key is a stat name (like "Name" or "Hit Points") and each value is the generated data as a string.
  • self.stat_widgets is a dictionary that maps from those same stat names to the QLabel widgets that display them.
  • generate_npc() gets the stats dictionary and loops through it. For each stat, it looks up the matching widget and calls setText().

Adding a new stat is now a two-step process: add a generator method to NPCGen, and add the stat name to the list in __init__. The loop takes care of creating the label and wiring everything up.

Why This Structure Works Well

There are a few design choices in this example worth highlighting:

The generator class knows nothing about the GUI. NPCGen just produces data and returns it. It doesn't import anything from PyQt6, and it doesn't try to update any widgets. This means you could reuse it in a command-line tool, a web app, or anywhere else.

The window class knows nothing about how data is generated. MainWindow calls self.npc_gen.generate_all() and gets back a dictionary. It doesn't care whether the data comes from a CSV file, a database, or random number generation. If you later change how names are generated, the window code stays the same.

Dictionaries keep related things together. Instead of 15 separate self.xxx_label attributes, you have one self.stat_widgets dictionary. The update logic is a simple loop rather than a long list of repetitive setText calls.

When Would You Need Signals?

For the structure described above — where clicking a button triggers data generation and updates labels — connecting the button's clicked signal directly to a method is perfectly sufficient. If you're new to signals and slots, see the Signals, Slots & Events tutorial for a full introduction.

Custom signals become useful when you need to pass data between separate classes that shouldn't have direct references to each other. For example, if your NPCGen class were doing work in a background thread and needed to notify the GUI when new data was ready, a custom signal would be the right way to do that — see Multithreading PyQt6 Applications with QThreadPool for how to handle that. But for a straightforward "click button, generate data, update labels" flow, the approach shown here is clean and appropriate.

Complete Working Example

Here's the full example in one piece, ready to copy and run. If you haven't set up PyQt6 yet, see the installation guide to get started.

python
import sys
import random

from PyQt6.QtWidgets import (
    QApplication, QWidget, QLabel, QPushButton,
    QVBoxLayout, QHBoxLayout,
)
from PyQt6.QtGui import QFont


class NPCGen:
    """Generates random NPC attributes."""

    FIRST_NAMES = ["Kira", "Dash", "Orla", "Bren", "Vex", "Zara"]
    LAST_NAMES = ["Solaris", "Voidwalker", "Ashborne", "Duskwood", "Starfall"]
    SPECIES = ["Human", "Twi'lek", "Wookiee", "Rodian", "Zabrak"]
    HOMEWORLDS = ["Coruscant", "Tatooine", "Naboo", "Kashyyyk", "Corellia"]
    OCCUPATIONS = ["Smuggler", "Bounty Hunter", "Mechanic", "Pilot", "Merchant"]

    def generate_name(self):
        first = random.choice(self.FIRST_NAMES)
        last = random.choice(self.LAST_NAMES)
        return f"{first} {last}"

    def generate_hitpoints(self):
        return random.randint(10, 100)

    def generate_species(self):
        return random.choice(self.SPECIES)

    def generate_homeworld(self):
        return random.choice(self.HOMEWORLDS)

    def generate_occupation(self):
        return random.choice(self.OCCUPATIONS)

    def generate_all(self):
        """Generate a complete set of NPC stats as a dictionary."""
        return {
            "Name": self.generate_name(),
            "Species": self.generate_species(),
            "Homeworld": self.generate_homeworld(),
            "Occupation": self.generate_occupation(),
            "Hit Points": str(self.generate_hitpoints()),
        }


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("NPC Generator")
        self.setMinimumWidth(350)

        self.npc_gen = NPCGen()

        # The stat names, in the order we want them displayed.
        self.stat_names = [
            "Name", "Species", "Homeworld", "Occupation", "Hit Points",
        ]

        main_layout = QVBoxLayout()

        # Build a row of labels for each stat.
        self.stat_widgets = {}
        for stat_name in self.stat_names:
            row_layout = QHBoxLayout()

            label = QLabel(f"{stat_name}:")
            label.setFont(QFont("Arial", 10, QFont.Bold))
            label.setFixedWidth(100)
            row_layout.addWidget(label)

            value_label = QLabel("—")
            value_label.setFont(QFont("Arial", 10))
            row_layout.addWidget(value_label)

            main_layout.addLayout(row_layout)
            self.stat_widgets[stat_name] = value_label

        # Add the Generate button at the bottom.
        generate_button = QPushButton("Generate NPC")
        generate_button.clicked.connect(self.generate_npc)
        main_layout.addWidget(generate_button)

        self.setLayout(main_layout)

        # Generate an initial NPC so the window isn't empty.
        self.generate_npc()

    def generate_npc(self):
        """Generate new random stats and display them."""
        stats = self.npc_gen.generate_all()
        for stat_name, value in stats.items():
            self.stat_widgets[stat_name].setText(value)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Each time you click "Generate NPC," every label updates with freshly generated random values. To add a new stat, add a method to NPCGen, include it in generate_all(), and add its name to self.stat_names in the window class. The rest takes care of itself.

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

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick

(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!

More info Get the book

Martin Fitzpatrick

Updating PyQt6 Widgets from Functions and 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.