PAYATHON 2026

QSqlQueryModel 行数正确却显示空白,为什么还会递归报错?

数据阿明

问题出在重写的 data():它没有处理 EditRole,也没有将其他角色交给父类。record(row) 内部会通过 EditRole 读取各列,因此第一种写法得到 None。第二种写法把 record(row) 移到角色判断之前,又导致了递归调用。

可以在自定义角色的处理中直接调用 super().data(),获取目标列的数据,并在方法末尾保留父类回退。

为什么行数和列数正确,字段值却是 None?

Qt 的 QSqlQueryModel::record(row) 会复制字段结构,再逐列调用模型的虚函数 data(..., Qt::EditRole) 填入值,从而进入你重写的 Python data()。具体调用关系可见 Qt 源码。

你的实现只处理了 DisplayRole 和 ToolTipRole。收到 EditRole 时,函数直接结束,Python 隐式返回 None。调用过程如下:

data(index, DisplayRole)
  → record(row)
    → data(各列索引, EditRole)
      → 返回 None
  → record.value("name") 得到 None

记录仍保留 3 个字段,所以 count() 为 3。行数也不取决于显示角色返回的内容,因此可能出现“有 4 行,但全部空白”的现象。

为什么提前读取 record(row) 会递归?

第二种写法在判断角色之前执行:

rec = self.record(r)

当 record(r) 内部请求 EditRole 时,又会进入同一个 data(),再次执行 self.record(r):

data()
  → record(row)
    → data(..., EditRole)
      → record(row)
        → data(..., EditRole)
          → ...

递归发生在角色判断之前,因此补上后面的 if 分支也无法阻止它。

如何实现姓名显示和年龄提示?

对于原查询 SELECT id, name, age FROM people,列号依次为 0、1、2。可以将 ListModel 替换为:

class ListModel(QSqlQueryModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setQuery("SELECT id, name, age FROM people")

        if self.lastError().isValid():
            raise RuntimeError(self.lastError().text())

    def data(self, index, role=Qt.ItemDataRole.DisplayRole):
        if not index.isValid():
            return None

        if role == Qt.ItemDataRole.DisplayRole:
            name_index = self.index(index.row(), 1)
            return super().data(
                name_index, Qt.ItemDataRole.DisplayRole
            )

        if role == Qt.ItemDataRole.ToolTipRole:
            age_index = self.index(index.row(), 2)
            age = super().data(
                age_index, Qt.ItemDataRole.EditRole
            )
            return None if age is None else str(age)

        return super().data(index, role)

代码通过 super().data() 调用父类实现,直接读取对应列,避免再次进入当前重写的方法。年龄转换为字符串后返回,供工具提示显示。

这段代码已经把 DisplayRole 映射到姓名列,因此不需要另外设置 setModelColumn(1)。末尾的父类回退保留了 EditRole 等角色的默认行为。

可以继续使用 record(row) 吗?

可以。对于第一种写法,只要在末尾添加:

return super().data(index, role)

并继续将 self.record(r) 放在 DisplayRole、ToolTipRole 分支内部,就能让内部的 EditRole 请求正常交给父类。

不要把 record(r) 无条件放在方法开头。即使写成 super().record(r),该方法内部仍会通过虚函数调用你的 data(),因此无法绕开递归。

如果只是想按字段名称查找列号,可以使用无参数的 self.record().indexOf("name")。无参数的 record() 提供字段信息,不负责填入某一行的数据,参见 Qt 的 record() 文档。

还有哪些注意事项?

  • 改用 QSqlTableModel 不能自动修复重写方法中的角色处理问题。仍应保留 super().data(index, role),让对应父类处理默认取值。
  • 示例中的固定列号依赖当前 SELECT 顺序;调整查询字段后,要同步修改映射。
  • 主程序应先创建 QApplication,再初始化 SQL 连接和模型。初始化时也应检查 con.open() 与 query.exec() 的返回值。查询模型的错误可通过 lastError() 查看。

备注:内容仅供参考。