Understanding .data() in Qt ModelView Models

How views retrieve data from your model using the data() method
Heads up! You've already completed this tutorial.

I'm confused about the data() method that overrides QAbstractListModel. It never seems to actually be invoked, despite being explained as the only way views can retrieve data from the model. How does the view actually get data from the model, and how are roles assigned to the data values?

This is a really common source of confusion when you're getting started with Qt's ModelView architecture. The data() method is being called — but not by your code. It's called automatically by the view. Let's walk through how this works and why it can feel invisible.

How ModelView works

In Qt's ModelView architecture, you have two main players:

  • The model — holds and manages your data.
  • The view — displays the data to the user.

The view never accesses your underlying data (like a Python list) directly. Instead, it asks the model for data by calling the model's data() method. This happens behind the scenes — Qt handles the communication for you. You never need to call data() yourself.

Here's a simple example to make this concrete. We'll create a custom model backed by a plain Python list, and connect it to a QListView:

python
import sys

from PyQt6.QtCore import QAbstractListModel, Qt
from PyQt6.QtWidgets import QApplication, QListView


class TodoModel(QAbstractListModel):
    def __init__(self, todos=None):
        super().__init__()
        self.todos = todos or []

    def rowCount(self, parent=None):
        return len(self.todos)

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self.todos[index.row()]


app = QApplication(sys.argv)

model = TodoModel(todos=["Buy milk", "Clean house", "Walk the dog"])

view = QListView()
view.setModel(model)
view.show()

sys.exit(app.exec())

When you run this, you'll see a list with three items. You didn't call data() anywhere — but it was called, many times, by the QListView.

The view calls data(), not you

When you call view.setModel(model), the view takes a reference to your model. From that point on, whenever the view needs to draw itself — for example, when the window first appears, or when the user scrolls — it calls your model's data() method for each visible item.

The view essentially says: "Model, what should I display for row 0? What about row 1?" and so on. Each of those questions is a call to data().

You can verify this by adding a print() inside your data() method:

python
def data(self, index, role):
    print(f"data() called for row {index.row()}, role {role}")
    if role == Qt.ItemDataRole.DisplayRole:
        return self.todos[index.row()]

Run the app again and you'll see data() being called multiple times in the console output. The view is doing this automatically.

What are roles?

You'll notice that data() receives two arguments: an index (which tells you which item the view is asking about) and a role (which tells you what kind of data the view wants).

A single item in your model can provide different data for different purposes. The role parameter is how the view specifies what it needs. Some common roles include:

Role Value Purpose
Qt.ItemDataRole.DisplayRole 0 The text to display
Qt.ItemDataRole.DecorationRole 1 An icon or color to show beside the text
Qt.ItemDataRole.ToolTipRole 3 Text shown when hovering over the item
Qt.ItemDataRole.BackgroundRole 8 Background color for the item
Qt.ItemDataRole.ForegroundRole 9 Text color for the item

The view calls data() multiple times for the same item, each time with a different role. For DisplayRole it wants the text. For DecorationRole it wants an icon. For BackgroundRole it wants a color. Your data() method decides what to return for each role.

Here's an example that returns different data depending on the role:

python
import sys

from PyQt6.QtCore import QAbstractListModel, Qt
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import QApplication, QListView


class TodoModel(QAbstractListModel):
    def __init__(self, todos=None):
        super().__init__()
        self.todos = todos or []

    def rowCount(self, parent=None):
        return len(self.todos)

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self.todos[index.row()]

        if role == Qt.ItemDataRole.ToolTipRole:
            return f"This is task #{index.row() + 1}"

        if role == Qt.ItemDataRole.BackgroundRole:
            if index.row() % 2 == 0:
                return QColor("#e6f7ff")


app = QApplication(sys.argv)

model = TodoModel(todos=["Buy milk", "Clean house", "Walk the dog"])

view = QListView()
view.setModel(model)
view.show()

sys.exit(app.exec())

In this version, even-numbered rows get a light blue background, and hovering over any item shows a tooltip. All of this is driven by the view calling data() with different roles.

Why you don't call data() yourself

In your own application code — for example, when adding or removing items — you work directly with the underlying data structure (the Python list). You don't need to go through data() to access your own data. The data() method exists as an interface for the view.

Think of it this way: the Python list is your internal storage, and data() is the public-facing window that the view looks through. You manage the list; the view reads from the model through data().

When you modify the underlying data, you do need to tell the model that something changed so the view knows to refresh. You do this by emitting signals. For example, after changing data, you can call self.layoutChanged.emit() to tell connected views to re-read everything:

python
import sys

from PyQt6.QtCore import QAbstractListModel, Qt
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import (
    QApplication,
    QListView,
    QPushButton,
    QVBoxLayout,
    QWidget,
)


class TodoModel(QAbstractListModel):
    def __init__(self, todos=None):
        super().__init__()
        self.todos = todos or []

    def rowCount(self, parent=None):
        return len(self.todos)

    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            return self.todos[index.row()]

        if role == Qt.ItemDataRole.ToolTipRole:
            return f"This is task #{index.row() + 1}"

        if role == Qt.ItemDataRole.BackgroundRole:
            if index.row() % 2 == 0:
                return QColor("#e6f7ff")

    def add_todo(self, text):
        self.todos.append(text)
        self.layoutChanged.emit()


app = QApplication(sys.argv)

model = TodoModel(todos=["Buy milk", "Clean house", "Walk the dog"])

window = QWidget()
layout = QVBoxLayout()

view = QListView()
view.setModel(model)
layout.addWidget(view)

counter = [0]


def add_item():
    counter[0] += 1
    model.add_todo(f"New task #{counter[0]}")


button = QPushButton("Add Task")
button.clicked.connect(add_item)
layout.addWidget(button)

window.setLayout(layout)
window.show()

sys.exit(app.exec())

Each time you click "Add Task", a new item is added to the internal list, layoutChanged is emitted, and the view automatically calls data() again for every visible row to refresh the display.

Putting it all together

Here's a summary of how the pieces fit:

  1. You subclass QAbstractListModel and implement rowCount() and data().
  2. You connect the model to a view using view.setModel(model).
  3. The view automatically calls data() whenever it needs to display or update items.
  4. The role parameter tells your data() method what kind of information the view is requesting.
  5. You return the appropriate data for each role, or None for roles you don't handle.
  6. When you change the underlying data, you emit a signal (like layoutChanged) so the view knows to call data() again.

The data() method is the bridge between your data and the view. You define it, and Qt calls it for you. That's why you never see an explicit call to data() in your own code — but it's working behind the scenes every time the view draws itself.

If you want to use this same approach with table data from numpy or pandas, take a look at how to display data in a QTableView using models. For building your own reusable widgets to use alongside model views, see the guide to creating custom widgets.

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

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

More info Get the book

Martin Fitzpatrick

Understanding .data() in Qt ModelView Models 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.