【问题标题】:PyQt5 Crash with QFileSystemModel and QSortFilterProxyModel..doing something wrong?PyQt5 Crash with QFileSystemModel 和 QSortFilterProxyModel.. 做错了什么?
【发布时间】:2017-06-27 23:23:28
【问题描述】:

在 Ubuntu 上使用 pyqt5 导航目录结构时,我遇到了一个间歇性的崩溃问题。有时只是导航到文件夹崩溃,有时它是由对正在显示的目录的外部更改触发的。下面的代码是后者的一个例子。

我正在构建的应用程序会检查程序激活时显示的目录。如果它不再是有效目录,它会向上移动到下一个有效目录。下面的代码通过创建一个目录结构、向下导航到一个文件并选择它、重命名树、然后从不再有效的目录移动到原来的目录来模拟这种情况。如果我多次运行此代码,我会看到它崩溃,有时在第一次执行时,有时在运行几次后。

我尝试根据在 gdb 堆栈跟踪中看到的代理模型函数来更新模式。这似乎解决了我的问题,尽管这对我来说似乎是错误的。

尽管我对解决方案不满意,但我已将其注销为已修复,但今天我再次遇到此代码崩溃,即使模型更新也是如此。

没有很多示例代码展示如何一起使用这些类。有人知道我在哪里做错了吗?

import os, sys, tempfile
from PyQt5 import QtCore, QtWidgets


class Widget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)
        self._view = QtWidgets.QTableView()
        layout.addWidget(self._view)

        self._model = QtWidgets.QFileSystemModel()
        self._model.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.AllEntries)

        self._proxy = QtCore.QSortFilterProxyModel(self)
        self._proxy.setSourceModel(self._model)
        self._view.setModel(self._proxy)

        header = self._view.horizontalHeader()
        header.setSortIndicator(0, QtCore.Qt.AscendingOrder)
        self._proxy.sort(header.sortIndicatorSection(), header.sortIndicatorOrder())

        # Create a temporary directory structure (tmpxyz/a/b/c) starting at the location of this file
        # This is oddly very specific..less nesting or no file to select doesn't exhibit the problem!?
        path = os.path.dirname(os.path.abspath(__file__))
        self.temp_dir = tempfile.TemporaryDirectory(dir=path)
        print('created temporary directory', self.temp_dir.name)
        foo_dir = os.path.join(self.temp_dir.name, 'foo')
        os.mkdir(foo_dir)
        self.a_dir = os.path.join(foo_dir, 'a')
        os.mkdir(self.a_dir)
        b_dir = os.path.join(self.a_dir, 'b')
        os.mkdir(b_dir)
        c_dir = os.path.join(b_dir, 'c')
        os.mkdir(c_dir)
        self.temp_file_name = os.path.join(c_dir, 'foo.txt')
        with open(self.temp_file_name, 'w') as txtfile:
            print('foo', file=txtfile)

        self._model.setRootPath(c_dir)

        # moved the following to loaded..didn't seem to help
        # source_index = self._model.index(c_dir)
        # index = self._proxy.mapFromSource(source_index)
        # self._view.setRootIndex(index)

        QtCore.QTimer.singleShot(4000, self._select_temp_file)
        QtCore.QTimer.singleShot(5000, self._rename_test_dir)

        self._model.directoryLoaded.connect(self._loaded)

    def _loaded(self):
        print('_loaded')
        path = self._model.rootPath()
        print('_loaded', path)
        source_index = self._model.index(path)
        index = self._proxy.mapFromSource(source_index)
        self._view.setRootIndex(index)

    def _select_temp_file(self):
        source_index = self._model.index(self.temp_file_name)
        if source_index.isValid():
            index = self._proxy.mapFromSource(source_index)
            self._view.setCurrentIndex(index)

    # Simulate an external application moving the current directory
    def _rename_test_dir(self):
        os.rename(self.a_dir, self.a_dir+'x')
        path = self._model.rootPath()
        print('_rename_test_dir', path)
        if not os.path.isdir(path):
            def _make_valid_path(path_):
                if os.path.isdir(path_):
                    return path_
                while path_:
                    path_, _ = os.path.split(path_)
                    if os.path.isdir(path_):
                        return path_
                return '/'

            print('Path not valid:', path)
            path = _make_valid_path(path)
            print('New Path:', path)

            self._model.setRootPath(path)
            print('_rename_test_dir 5')
            # Using gdb showed a crash in the proxy model..so update it here...decreases crash frequency
            # self._proxy.setSourceModel(self._model)
            # print('_rename_test_dir 6')
            # self._view.setModel(self._proxy)
            print('_rename_test_dir 7')

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    widget = Widget()
    widget.show()
    sys.exit(app.exec_())

更新:

如果从代码中看不清楚,程序不需要交互,交互会干扰测试。创建目录结构后,它会自动进入结构的叶级。 4 秒后它将选择 foo.txt 文件。再过一秒钟,它将重命名树中的“a”目录,然后向上导航到“a”以前的位置。如果一切顺利,将显示 foo 目录,其中可以看到 'ax' 目录,并且可以退出并重试程序。

在 gbd 下执行大约需要 8 次才能获得以下堆栈跟踪。编辑到最后 2 次运行:

gdb --args python3 test_qfilesystemmodel_crash2.py  
(gdb) run
Starting program: /usr/bin/python3 test_qfilesystemmodel_crash2.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7fffe9ea5700 (LWP 21128)]
[New Thread 0x7fffdb5e5700 (LWP 21129)]
[New Thread 0x7fffdade4700 (LWP 21130)]
created temporary directory /home/myuser/Desktop/haiqu/src/tmp8dl0_uny
_loaded
_loaded /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo/a/b/c
_rename_test_dir /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo/a/b/c
Path not valid: /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo/a/b/c
New Path: /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo
_rename_test_dir 5
_rename_test_dir 7
_loaded
_loaded /home/myuser/Desktop/haiqu/src/tmp8dl0_uny/foo
[Thread 0x7fffdb5e5700 (LWP 21129) exited]
[Thread 0x7fffe9ea5700 (LWP 21128) exited]
[Thread 0x7ffff7fbb700 (LWP 21127) exited]
[Inferior 1 (process 21127) exited normally]
(gdb) run
Starting program: /usr/bin/python3 test_qfilesystemmodel_crash2.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7fffe9ea5700 (LWP 21132)]
[New Thread 0x7fffdb5e5700 (LWP 21133)]
[New Thread 0x7fffdade4700 (LWP 21134)]
created temporary directory /home/myuser/Desktop/haiqu/src/tmpwr560n5l
_loaded
_loaded /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo/a/b/c
_rename_test_dir /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo/a/b/c
Path not valid: /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo/a/b/c
New Path: /home/myuser/Desktop/haiqu/src/tmpwr560n5l/foo
_rename_test_dir 5
_rename_test_dir 7

Thread 1 "python3" received signal SIGSEGV, Segmentation fault.
0x00007ffff5062e89 in QSortFilterProxyModel::sibling(int, int, QModelIndex const&) const ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
(gdb) bt
#0  0x00007ffff5062e89 in QSortFilterProxyModel::sibling(int, int, QModelIndex const&) const ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#1  0x00007ffff568d2d4 in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtCore.cpython-35m-x86_64-linux-gnu.so
#2  0x00007ffff0af35ff in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#3  0x00007ffff00f9779 in QAccessible::updateAccessibility(QAccessibleEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Gui.so.5
#4  0x00007ffff0b028cc in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#5  0x00007ffff0b07486 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#6  0x00007ffff50cb4a9 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#7  0x00007ffff51426e4 in QAbstractItemModel::rowsInserted(QModelIndex const&, int, int, QAbstractItemModel::QPrivateSignal) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#8  0x00007ffff5046f7b in QAbstractItemModel::endInsertRows() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#9  0x00007ffff5068246 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#10 0x00007ffff506a685 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#11 0x00007ffff506e089 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#12 0x00007ffff50cb4a9 in QMetaObject::activate(QObject*, int, int, void**) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#13 0x00007ffff51426e4 in QAbstractItemModel::rowsInserted(QModelIndex const&, int, int, QAbstractItemModel::QPrivateSignal) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#14 0x00007ffff5046f7b in QAbstractItemModel::endInsertRows() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#15 0x00007ffff0acf6e6 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#16 0x00007ffff0ad51f9 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#17 0x00007ffff50cc359 in QObject::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#18 0x00007ffff0ad58b2 in QFileSystemModel::event(QEvent*) () from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#19 0x00007ffff131ecdb in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-35m-x86_64-linux-gnu.so
#20 0x00007ffff08b635c in QApplicationPrivate::notify_helper(QObject*, QEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#21 0x00007ffff08bdb11 in QApplication::notify(QObject*, QEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Widgets.so.5
#22 0x00007ffff124cdee in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-35m-x86_64-linux-gnu.so
#23 0x00007ffff509f8a0 in QCoreApplication::notifyInternal2(QObject*, QEvent*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#24 0x00007ffff50a202d in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#25 0x00007ffff50f3b03 in ?? () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#26 0x00007ffff3b35377 in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#27 0x00007ffff3b355e0 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#28 0x00007ffff3b3568c in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#29 0x00007ffff50f3f0f in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#30 0x00007ffff509d88a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) ()
   from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#31 0x00007ffff50a5ffc in QCoreApplication::exec() () from /usr/lib/x86_64-linux-gnu/libQt5Core.so.5
#32 0x00007ffff1210ddb in ?? () from /usr/lib/python3/dist-packages/PyQt5/QtWidgets.cpython-35m-x86_64-linux-gnu.so
#33 0x000055555571b28f in PyCFunction_Call ()
#34 0x00005555556d21e9 in PyEval_EvalFrameEx ()
#35 0x00005555556d6d16 in ?? ()
#36 0x00005555556d7a1f in PyEval_EvalCode ()
#37 0x00005555557a4b02 in ?? ()
#38 0x00005555557a6f8d in PyRun_FileExFlags ()
#39 0x00005555557a772e in PyRun_SimpleFileExFlags ()
#40 0x00005555557d72e7 in Py_Main ()
#41 0x0000555555667b31 in main ()
(gdb) 

【问题讨论】:

  • 我测试过代码没有产生任何问题。
  • 我已经按照指示运行了您的测试 20 次,没有遇到任何问题,您能否告诉我们您使用的 python 和 pyqt 版本以及您的操作系统版本是什么?
  • @eyllanesc python3 --version Python 3.5.3 ...qt 5.7.1 uname -a Linux ibp 4.10.0-24-generic #28-Ubuntu SMP Wed Jun 14 08:14:34 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux lsb_release -a 没有可用的 LSB 模块。发行商 ID:Ubuntu 描述:Ubuntu 17.04 发行版:17.04 代号:zesty
  • 发布的示例在安装了 pip 的 qtqt5 5.8.0 的虚拟环境中不会崩溃(尽管原始应用程序代码确实如此)。我必须在 5.8 中找到一个可重现的测试用例。
  • 我已经用 PyQt 5.8.2, Python 3.6.1 in archilinux Kernel: 4.11.6-1-ARCH 测试过

标签: python python-3.x pyqt pyqt5


【解决方案1】:

我把它写成一个旧的图书馆问题。更新到 pyqt5.8 和 python3.6 后,我不再能够重现崩溃。感谢您的帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-17
    • 2011-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多