web-dev-qa-db-ja.com

PyQtでのクラッシュ/ハングを回避するためのグッドプラクティスは何ですか?

私はpythonとQtの両方が大好きですが、Qtがpythonを念頭に置いて設計されていないことは明らかです。PyQt/をクラッシュさせる方法はたくさんあります。 PySideアプリケーション。適切なツールを使用しても、その多くはデバッグが非常に困難です。

知りたいのですが、PyQtとPySideを使用するときにクラッシュやロックアップを回避するためのグッドプラクティスは何ですか?これらは、一般的なプログラミングのヒントやサポートモジュールから、回避すべき非常に具体的な回避策やバグまで、何でもかまいません。

35
Luke

一般的なプログラミング慣行

  • マルチスレッドコードを使用する必要がある場合は、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で設定)がすべて削除されます。

      # 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への参照を保持してはなりません(weakrefは問題ありません)。
  • QTimer.singleShotを繰り返し使用すると、ロックアップが発生する可能性があります。
  • QGLWidgetでQGraphicsViewを使用することは避けてください。

出口のクラッシュを回避するためのプラクティス

  • QGraphicsSceneの一部ではないQGraphicsItemは、終了時にクラッシュを引き起こす可能性があります。
  • 親または任意の祖先を参照するQObjectは、出口クラッシュを引き起こす可能性があります。
  • 親がないQGraphicsSceneは、出口クラッシュを引き起こす可能性があります。
  • 出口のクラッシュを回避する最も簡単な方法は、pythonがQtオブジェクトの収集を開始する前に、os._exit()を呼び出すことです。ただし、プログラムの一部が適切な出口処理に依存している可能性があるため、これは危険な場合があります。正しく機能するには(たとえば、ログファイルを終了したり、デバイスハンドルを適切に閉じたりします)。少なくとも、os._exit()を呼び出す前に、手動でatexitコールバックを呼び出す必要があります。
51
Luke

参考までに、 PyQtの作者からのコメント をルークの回答に投稿します:「それはゴミだ」。

誰かがこの投稿に飛び込んで、これらすべての(存在しない)「問題」に戸惑い続ける可能性があるので、それは重要だと思います。

4
nenad

要点を追加するだけです:

Qtベースのプログラムでスレッドを使用する必要がある場合は、自動ガベージコレクターを無効にし、メインスレッドで手動収集を行う必要があります( http://pydev.blogspot.com.br/2014/で説明されているように) 03/should-python-garbage-collector-be.html )-オブジェクトにサイクルがないことを確認した場合でも、これを行う必要があることに注意してください(サイクルでは、基本的にオブジェクトを=までライブにしますpythonサイクリックガベージコレクターが発生しますが、例外として何かがある場合、フレームが存続する可能性があるため、そのような状況では、オブジェクトが予想よりも長く存続する可能性があります)...そのような場合、ガベージコレクターがセカンダリスレッドにぶつかり、qtがsegfaultする可能性があります(qtウィジェットは常にメインスレッドで収集する必要があります)。

4
Fabio Zadrozny