PyQt5中使用PandasModel显示SQL Server 2019表列表时出现'pyodbc.Cursor'对象无'index'属性错误的解决方法
解决PyQt5 PandasModel结合SQL Server时的'pyodbc.Cursor'无'index'属性错误
你遇到的错误核心原因非常清晰:你将pyodbc.Cursor对象直接传给了PandasModel,但PandasModel的设计是接收Pandas DataFrame作为数据源。
问题分析
当你执行df = sql_conn.execute(query_string)时,返回的是一个pyodbc.Cursor实例——它只是数据库查询结果的迭代器,并非DataFrame。而你的PandasModel类中,多个方法(比如rowCount里的len(self._df.index)、headerData里的self._df.columns)都依赖DataFrame的专属属性,游标对象根本没有这些属性,所以触发了'pyodbc.Cursor' object has no attribute 'index'错误。
你注释掉的pd.read_sql_query才是正确的方向,只是之前可能因为一些小细节没跑通,下面是修正后的完整方案:
修正步骤
- 用
pd.read_sql_query生成标准DataFrame:这个方法会直接把SQL查询结果转换成符合PandasModel要求的DataFrame,是连接SQL和PyQt表格的正确方式。 - 移除不必要的事务提交:查询操作不需要调用
commit(),只有修改数据的操作(如INSERT/UPDATE/DELETE)才需要提交事务。 - 优化SQL语句与连接逻辑:避免使用
use语句,直接在连接字符串中指定目标数据库,减少游标上下文的潜在问题。
修正后的完整代码
from PyQt5 import QtCore, QtGui, QtWidgets import pyodbc import pandas as pd class PandasModel(QtCore.QAbstractTableModel): def __init__(self, df = pd.DataFrame(), parent=None): QtCore.QAbstractTableModel.__init__(self, parent=parent) self._df = df def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): if role != QtCore.Qt.DisplayRole: return None if orientation == QtCore.Qt.Horizontal: try: return self._df.columns.tolist()[section] except IndexError: return None elif orientation == QtCore.Qt.Vertical: try: return self._df.index.tolist()[section] except IndexError: return None def data(self, index, role=QtCore.Qt.DisplayRole): if role != QtCore.Qt.DisplayRole: return None if not index.isValid(): return None return str(self._df.iloc[index.row(), index.column()]) def setData(self, index, value, role): row = self._df.index[index.row()] col = self._df.columns[index.column()] if hasattr(value, 'toPyObject'): value = value.toPyObject() else: dtype = self._df[col].dtype if dtype != object: value = None if value == '' else dtype.type(value) self._df.at[row, col] = value # 替换过时的set_value方法 return True def rowCount(self, parent=QtCore.QModelIndex()): return len(self._df.index) def columnCount(self, parent=QtCore.QModelIndex()): return len(self._df.columns) def sort(self, column, order): colname = self._df.columns.tolist()[column] self.layoutAboutToBeChanged.emit() self._df.sort_values(colname, ascending= order == QtCore.Qt.AscendingOrder, inplace=True) self._df.reset_index(inplace=True, drop=True) self.layoutChanged.emit() class Widget(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent=None) vLayout = QtWidgets.QVBoxLayout(self) hLayout = QtWidgets.QHBoxLayout() self.pathLE = QtWidgets.QLineEdit(self) hLayout.addWidget(self.pathLE) self.loadBtn = QtWidgets.QPushButton("load", self) hLayout.addWidget(self.loadBtn) vLayout.addLayout(hLayout) self.tableView = QtWidgets.QTableView(self) vLayout.addWidget(self.tableView) self.loadBtn.clicked.connect(self.loadData) self.tableView.setSortingEnabled(True) def loadData(self): dbName="AccountDatabase" server = '-----' # 替换成你的实际服务器地址 username = 'admin' password = 'admin#' # 直接在连接字符串中指定目标数据库,避免use语句的上下文问题 sql_conn = pyodbc.connect( 'DRIVER={ODBC Driver 17 for SQL Server};SERVER=' + server + ';DATABASE=' + dbName + ';UID=' + username + ';PWD=' + password ) query_string= "SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'" # 用pd.read_sql_query将查询结果转为DataFrame df = pd.read_sql_query(query_string, sql_conn) sql_conn.close() # 主动关闭连接,避免资源泄漏 model = PandasModel(df) self.tableView.setModel(model) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) w = Widget() w.show() sys.exit(app.exec_())
额外优化说明
- 替换了Pandas已过时的
df.set_value方法为推荐的df.at,提升代码兼容性。 - 把
QtCore.QVariant()替换成None,在Python3版本的PyQt5中,返回None会自动处理为空的QVariant,代码更简洁。 - 添加了数据库连接关闭操作,养成资源释放的好习惯。
内容的提问来源于stack exchange,提问作者Dhurjati Riyan
相关产品推荐
相关产品推荐

