When connecting a signal to a slot, how do you know what data the signal passes along? For example,
windowTitleChangedsends a string — but how would you find that out? With so many different signals in Qt (pressed, released, drag, move, etc.), how do we know what each signal sends when it's triggered?
When you first start working with signals and slots in PyQt6 or PySide6, the mechanism itself is fairly intuitive: something happens, and your connected function gets called. But at some point you'll notice that some signals pass data to your slot function, and you might wonder — where does that data come from, and how do I know what to expect?
Let's look at a quick example to set the scene:
self.windowTitleChanged.connect(self.on_window_title_change)
def on_window_title_change(self, s):
print(s)
Here, windowTitleChanged is a signal that gets emitted whenever the window title changes. It sends the new title as a string to whatever slot is connected. But how would you know that s is a string? And how do you figure this out for any signal?
The answer is: the Qt documentation.
Reading the Qt documentation
Every signal in Qt has a signature — a definition that tells you the name of the signal and what types of data it sends. You can find these signatures in the official Qt documentation.
For PySide6, the documentation lives at:
📄 https://doc.qt.io/qtforpython-6/
For PyQt6, you can use the main Qt C++ documentation (the signal signatures are the same):
Let's walk through how to look up a signal.
Looking up windowTitleChanged
The windowTitleChanged signal belongs to QWidget. If you search for QWidget in the Qt documentation, you'll find a page listing all its signals. Under the signals section, you'll see something like:
windowTitleChanged(const QString &title)
In the PySide6 documentation, this looks like:
windowTitleChanged(title)
with the parameter type listed as str.
This tells you that when the signal is emitted, it sends one piece of data: a string containing the new window title. That string is what arrives as the s parameter in your slot function.
What a signal signature tells you
A signal signature gives you two things:
- How many parameters the signal sends (zero, one, or more).
- What type each parameter is.
Here are a few common examples to give you a feel for the patterns:
| Signal | Belongs to | Signature | What it sends |
|---|---|---|---|
clicked |
QPushButton |
clicked(checked: bool) |
Whether the button is checked (for checkable buttons) |
textChanged |
QLineEdit |
textChanged(text: str) |
The new text in the input field |
valueChanged |
QSpinBox |
valueChanged(value: int) |
The new integer value |
currentIndexChanged |
QComboBox |
currentIndexChanged(index: int) |
The index of the newly selected item |
Some signals send no data at all. For example, QPushButton.pressed has an empty signature — it just tells you "the button was pressed" without any additional information. Your slot function doesn't need any parameters in that case.
Connecting signals with and without data
When a signal sends data, your slot function needs a parameter to receive it. When it doesn't, you leave the parameter out.
Here's a complete example showing both cases:
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QPushButton,
QLineEdit, QVBoxLayout, QWidget
)
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Signal Signatures")
layout = QVBoxLayout()
# QPushButton.pressed sends no data
button = QPushButton("Click me")
button.pressed.connect(self.on_button_pressed)
layout.addWidget(button)
# QLineEdit.textChanged sends a string
line_edit = QLineEdit()
line_edit.textChanged.connect(self.on_text_changed)
layout.addWidget(line_edit)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def on_button_pressed(self):
# No parameters — pressed sends nothing
print("Button was pressed!")
def on_text_changed(self, text):
# One parameter — textChanged sends the new text as a string
print(f"Text changed to: {text}")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Run this and try clicking the button and typing in the text field. You'll see the pressed signal triggers with no data, while textChanged passes along the current text each time it changes.
Signals with multiple parameters
Some signals send more than one piece of data. For example, QTableWidget.cellChanged sends both the row and column of the changed cell:
cellChanged(row: int, column: int)
Your slot would look like:
def on_cell_changed(self, row, column):
print(f"Cell at row {row}, column {column} changed")
Each parameter in the signal signature maps to a parameter in your slot function, in order.
A quick way to explore: just print it
If you're ever unsure what a signal is sending, you can connect it to a quick test function that prints whatever it receives. Python's *args syntax is helpful for this:
def debug_slot(*args):
print(args)
some_widget.some_signal.connect(debug_slot)
This will print a tuple of everything the signal sent. It's a handy way to explore while you're developing, even if you wouldn't leave it in production code.
Where to look things up
To summarize, when you want to know what a signal sends:
- Identify which class the signal belongs to. For example,
textChangedon aQLineEditbelongs toQLineEdit. - Search for that class in the Qt documentation. Look in the "Signals" section of the class page.
- Read the signal signature. It tells you the number and types of parameters.
For PySide6, the Qt for Python documentation shows Python types directly. For PyQt6, the main Qt docs use C++ types, but the mapping is straightforward: QString → str, int → int, bool → bool, double → float, and so on.
Over time, you'll start to remember the common ones — clicked sends a bool, textChanged sends a str, valueChanged sends an int or float depending on the widget. But whenever you encounter a new signal, the documentation is always there to check.
Complete working example
Here's a fuller example that demonstrates several different signal signatures in one window:
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QPushButton, QLineEdit,
QSpinBox, QComboBox, QVBoxLayout, QWidget, QLabel
)
import sys
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Exploring Signal Signatures")
layout = QVBoxLayout()
self.output_label = QLabel("Interact with the widgets below:")
layout.addWidget(self.output_label)
# QPushButton — clicked sends a bool (checked state)
button = QPushButton("Click me")
button.setCheckable(True)
button.clicked.connect(self.on_button_clicked)
layout.addWidget(button)
# QLineEdit — textChanged sends a str
line_edit = QLineEdit()
line_edit.setPlaceholderText("Type something...")
line_edit.textChanged.connect(self.on_text_changed)
layout.addWidget(line_edit)
# QSpinBox — valueChanged sends an int
spinbox = QSpinBox()
spinbox.setRange(0, 100)
spinbox.valueChanged.connect(self.on_value_changed)
layout.addWidget(spinbox)
# QComboBox — currentIndexChanged sends an int
combo = QComboBox()
combo.addItems(["Apple", "Banana", "Cherry"])
combo.currentIndexChanged.connect(self.on_index_changed)
layout.addWidget(combo)
# windowTitleChanged sends a str
self.windowTitleChanged.connect(self.on_title_changed)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def on_button_clicked(self, checked):
self.output_label.setText(
f"Button clicked — checked: {checked} (bool)"
)
def on_text_changed(self, text):
self.output_label.setText(
f"Text changed — value: '{text}' (str)"
)
def on_value_changed(self, value):
self.output_label.setText(
f"SpinBox changed — value: {value} (int)"
)
def on_index_changed(self, index):
self.output_label.setText(
f"ComboBox changed — index: {index} (int)"
)
def on_title_changed(self, title):
print(f"Window title changed to: '{title}' (str)")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Try interacting with each widget and watch the label update. Each slot receives exactly the data described by that signal's signature in the documentation — no more, no less. Once you get comfortable checking the docs, you'll always know what to expect.
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.