我最近在使用PyQt开发的一个项目中遇到了问题。我有一个连接到QAbstractItemModel的QTreeView,其中通常有成千上万个节点。到目前为止,它可以正常工作,但是我今天意识到选择很多节点非常缓慢。经过一些调查,发现QAbstractItemModel.parent()被频繁调用。我创建了最小化的代码来复现这个问题:
#!/usr/bin/env python
import sys
import cProfile
import pstats
from PyQt4.QtCore import Qt, QAbstractItemModel, QVariant, QModelIndex
from PyQt4.QtGui import QApplication, QTreeView
# 200 root nodes with 10 subnodes each
class TreeNode(object):
def __init__(self, parent, row, text):
self.parent = parent
self.row = row
self.text = text
if parent is None: # root node, create subnodes
self.children = [TreeNode(self, i, unicode(i)) for i in range(10)]
else:
self.children = []
class TreeModel(QAbstractItemModel):
def __init__(self):
QAbstractItemModel.__init__(self)
self.nodes = [TreeNode(None, i, unicode(i)) for i in range(200)]
def index(self, row, column, parent):
if not self.nodes:
return QModelIndex()
if not parent.isValid():
return self.createIndex(row, column, self.nodes[row])
node = parent.internalPointer()
return self.createIndex(row, column, node.children[row])
def parent(self, index):
if not index.isValid():
return QModelIndex()
node = index.internalPointer()
if node.parent is None:
return QModelIndex()
else:
return self.createIndex(node.parent.row, 0, node.parent)
def columnCount(self, parent):
return 1
def rowCount(self, parent):
if not parent.isValid():
return len(self.nodes)
node = parent.internalPointer()
return len(node.children)
def data(self, index, role):
if not index.isValid():
return QVariant()
node = index.internalPointer()
if role == Qt.DisplayRole:
return QVariant(node.text)
return QVariant()
app = QApplication(sys.argv)
treemodel = TreeModel()
treeview = QTreeView()
treeview.setSelectionMode(QTreeView.ExtendedSelection)
treeview.setSelectionBehavior(QTreeView.SelectRows)
treeview.setModel(treemodel)
treeview.expandAll()
treeview.show()
cProfile.run('app.exec_()', 'profdata')
p = pstats.Stats('profdata')
p.sort_stats('time').print_stats()
为了重现问题,只需运行代码(进行性能分析)并选择树部件中的所有节点(通过Shift选择或Cmd-A)。当您退出应用程序时,性能分析统计数据将显示类似以下内容:Fri May 8 20:04:26 2009 profdata
628377 function calls in 6.210 CPU seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
1 4.788 4.788 6.210 6.210 {built-in method exec_}
136585 0.861 0.000 1.182 0.000 /Users/hsoft/Desktop/slow_selection.py:34(parent)
142123 0.217 0.000 0.217 0.000 {built-in method createIndex}
17519 0.148 0.000 0.164 0.000 /Users/hsoft/Desktop/slow_selection.py:52(data)
162198 0.094 0.000 0.094 0.000 {built-in method isValid}
8000 0.055 0.000 0.076 0.000 /Users/hsoft/Desktop/slow_selection.py:26(index)
161357 0.047 0.000 0.047 0.000 {built-in method internalPointer}
94 0.000 0.000 0.000 0.000 /Users/hsoft/Desktop/slow_selection.py:46(rowCount)
404 0.000 0.000 0.000 0.000 /Users/hsoft/Desktop/slow_selection.py:43(columnCount)
94 0.000 0.000 0.000 0.000 {len}
1 0.000 0.000 6.210 6.210 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
这个数据中奇怪的部分是parent()函数被调用的次数:2000个节点中竟然调用了136k次!有人知道为什么吗?