PyQtでは循環参照に注意

Traceback (most recent call last):
  File "D:\Owner\temp\q.py", line 14, in callback
    self.setText(v)
RuntimeError: underlying C/C++ object has been deleted

PyQtを使った方なら、一度はこのRuntimeErrorに遭遇した事があると思います。
ウィジェットC++の部分が存在しない時のエラーです。

原因の大半は、ウィジェットのサブクラスで、スーパークラスコンストラクタを呼び忘れることです。

しかし、循環参照が原因の事もあるようです。

#encoding:shift-jis
from __future__ import division, print_function
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *

class SelfRefWidget(QLineEdit):
    def __init__(self):
        QLineEdit.__init__(self)
        self._spam = self.callback
    
    def callback(self, v):
        self.setText(v)
        
    def __del__(self):
        print("dying", self)
    
class ScrollArea(QScrollArea):
    signal = pyqtSignal(str)
    def __init__(self, *a, **kw):
        QScrollArea.__init__(self, *a, **kw)
        self.createWidget()
    
    def hello(self):
        self.signal.emit("hello")
    
    def createWidget(self):
        w = SelfRefWidget()
        self.signal.connect(w.callback)
        self.setWidget(w)
    
def main():
    app = QApplication(sys.argv)
    w = QWidget()
    e = ScrollArea()
    e.createWidget()
    e.hello()
    
if __name__ == "__main__":
    main()

このコードを実行すると、"underlying C/C++ object has been deleted"が表示されます。

"e.createWidget()"で、ScrollAreaの中身が新しく作られますが、
そのとき古い中身は、どこからも参照されないので破棄

・・・されるはずなのですが、循環参照があるためすぐには破棄されません。

そのためe.hello()で、古い中身のcallbackも呼ばれます。
しかし、どういうわけかSelfRefWidgetのC++部分は先に削除されているようです。
そのためself.setTextでエラーになります。


循環参照で注意なのが、束縛メソッドです。
メソッドの別名を作るつもりで、

self.setElmId = self.setId

とやってしまうと循環参照が発生してしまいます。