关于python:避免PyQt崩溃/挂起的好的做法是什么?

What are good practices for avoiding crashes / hangs in PyQt?

我同时喜欢python和Qt,但是对我来说很明显Qt并不是在设计时就考虑到python的。 有很多方法可以使PyQt / PySide应用程序崩溃,即使使用适当的工具,也很难调试很多方法。

我想知道:使用PyQt和PySide时避免崩溃和锁定的良好实践是什么? 从常规的编程技巧和支持模块到高度特定的解决方法和应避免的错误,这些都可以。


通用编程实践

  • 如果必须使用多线程代码,则永远不要从非GUI线程访问GUI。总是通过发出信号或其他线程安全机制将消息发送到GUI线程。
  • 注意"模型/查看任何内容"。 TableView,TreeView等。它们很难正确编程,并且任何错误都会导致无法跟踪的崩溃。使用模型测试可帮助确保模型内部一致。
  • 了解Qt对象管理与Python对象管理交互的方式以及可能出错的情况。参见http://python-camelot.s3.amazonaws.com/gpl/release/pyqt/doc/advanced/development.html

    • 没有父级的Qt对象由Python拥有;只有Python可以删除它们。
    • 带有父对象的Qt对象归Qt所有,如果删除了它们的父对象,则Qt将删除它们。
    • 示例:带有PyQt4的核心转储
  • QObject通常不应引用其父代或其任何祖先(可以使用弱引用)。这将最多导致内存泄漏,并偶尔导致崩溃。
  • 请注意Qt自动删除对象的情况。如果未通知python包装器删除了C ++对象,则对其进行访问将导致崩溃。由于PyQt和PySide在跟踪Qt对象方面遇到困难,因此可以通过许多不同的方式发生这种情况。

    • 复合控件,例如QScrollArea及其滚动条,QSpinBox及其QLineEdit等。(Pyside没有此问题)
    • 删除QObject会自动删除其所有子级(不过PyQt通常会正确处理此子级)。
    • 从QTreeWidget中删除项目将导致所有关联的小部件(通过QTreeWidget.setItemWidget设置)被删除。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      # Example:
      from PyQt4 import QtGui, QtCore
      app = QtGui.QApplication([])

      # Create a QScrollArea, get a reference to one of its scroll bars.
      w = QtGui.QWidget()
      sa = QtGui.QScrollArea(w)
      sb = sa.horizontalScrollBar()

      # Later on, we delete the top-level widget because it was removed from the
      # GUI and is no longer needed
      del w

      # At this point, Qt has automatically deleted all three widgets.
      # PyQt knows that the QScrollArea is gone and will raise an exception if
      # you try to access it:
      sa.parent()
      Traceback (most recent call last):
        File"<stdin>", line 1, in <module>
      RuntimeError: underlying C/C++ object has been deleted

      # However, PyQt does not know that the scroll bar has also been deleted.
      # Since any attempt to access the deleted object will probably cause a
      # crash, this object is 'toxic'; remove all references to it to avoid
      # any accidents
      sb.parent()
      # Segmentation fault (core dumped)

具体的解决方法/错误

  • 在不先调用prepareGeometryChange()的情况下更改QGraphicsItems的边界可能会导致崩溃。
  • 在QGraphicsItem.paint()内部引发异常可能导致崩溃。始终在paint()中捕获异常并显示一条消息,而不是让异常继续进行。
  • QGraphicsItems永远不要保留对其生活的QGraphicsView的引用。(可以使用弱引用)。
  • 重复使用QTimer.singleShot可能会导致锁定。
  • 避免将QGraphicsView与QGLWidget一起使用。

避免出口崩溃的实践

  • 不属于QGraphicsScene的QGraphicsItem可能导致退出时崩溃。
  • 引用其父级或任何祖先的QObject可能导致退出崩溃。
  • 没有父级的QGraphicsScene可能导致退出崩溃。
  • 避免退出崩溃的最简单方法是在python开始收集Qt对象之前调用os._exit()。但是,这可能很危险,因为程序的某些部分可能依赖于正确的退出处理才能正确运行(例如,终止日志文件或正确关闭设备句柄)。至少,应该在调用os._exit()之前手动调用atexit回调。


仅供参考,我将PyQt的作者对路加的回答发表评论:"这是垃圾。"

我认为这很重要,因为有人可以跳到这篇文章,并对所有这些(不存在的)"问题"感到困惑。


补充一点:

如果必须在基于qt的程序中使用线程,则实际上必须禁用自动垃圾收集器并在主线程上进行手动收集(如http://pydev.blogspot.com.br/2014/03/should- python-garbage-collector-be.html)-请注意,即使确保对象没有循环也应该这样做(通过循环,基本上可以使对象处于活动状态,直到python循环垃圾收集器崩溃为止,但是有时,如果您有例外情况,则可能会使某个帧保持活动状态,因此,在这种情况下,您的对象可能仍保持活动状态的时间比您预期的长)。在这种情况下,垃圾收集器可能会撞到在辅助线程中,这可能导致qt发生段错误(必须始终将qt小部件收集在主线程中)。