2017-12-22 8 views
2

オブジェクトがあり、そのメソッドの1つをPyQtシグナルが発行されたときに実行したいとします。そして、私はそれが信号によって渡されないパラメータでそうしたいと仮定します。だから私は、信号のスロットとしてラムダ作成:通常のPyQtのシグナルとスロットで、今ラムダのオブジェクトのライフタイムがpyqtSignalに接続されている

class MyClass(object): 
    def __init__(self, model): 
     model.model_changed_signal.connect(lambda: self.set_x(model.x(), silent=True)) 

を、信号接続は、ガベージコレクションを防ぐことはできません。接続されたスロットのオブジェクトがガーベッジ・コレクションされると、対応する信号が放出されたときにスロットはもはや呼び出されなくなります。

しかし、lambdaを使用するとどのように動作しますか?私はラムダへの参照を格納していませんが、シグナルスロット接続は機能し続けます。したがってラムダはガベージコレクションされません。

MyClassのインスタンスをNoneに設定すると、そのインスタンスはガベージコレクションされません。model_changed_signalを発行すると、ラムダは正常に実行されます。だから明らかに、MyClassのインスタンスへの参照はどこかで(おそらくラムダの文脈で)保持されています - 私は望んでいません。

どうしてですか?

答えて

2

例のlambdaはクロージャを形成します。つまり、囲みスコープで使用可能なオブジェクトを参照するネストされた関数です。クロージャを作成するすべての関数は、参照を維持するために必要なすべての項目に対して、cell objectを保持します。あなたの例では

lambda__init__方法の範囲内のローカルselfmodel変数への参照でクロージャを作成します。 lambdaへの参照をどこかに保持しておけば、__closure__属性を使ってクロージャのすべてのセルオブジェクトを調べることができます。あなたの例では、それはこのような何か表示していました:あなたはここに示したMyModelMyClassオブジェクトへの他のすべての参照を削除した場合

>>> print(func.__closure__) 
(<cell at 0x7f99c16c5138: MyModel object at 0x7f99bbbf0948>, <cell at 0x7f99c16c5168: MyClass object at 0x7f99bbb81390>) 

を、細胞によって保たれたものが残ってしまいます。したがって、オブジェクトのクリーンアップについては、関連するオブジェクトに対してクロージャを形成する可能性のある関数に接続されているすべてのシグナルを常に明示的に切断する必要があります。


注意が/スロット接続を通知するために来るとき、PyQtは扱いが異なるC++とPythonスロットインスタンスメソッドを包んだこと。 lambda、定義された関数、部分的なオブジェクト、および静的メソッドは、シグナルに接続されているときに、これらのタイプの呼び出し可能型の参照カウントはではなく、です。これは、後者のタイプの呼び出し可能コードへの他のすべての参照が削除された場合、残りの信号接続がそれらを生き続けることを意味します。信号を切断すると、必要に応じて、そのような接続された呼び出し可能なものがガベージコレクションされます。

上記の例外の1つは、クラスメソッドです。これらへの接続を作成するときにPyQtは、特別なラッパーを作成し、ので、それらへの他のすべての参照が削除され、信号が放出されている場合、例外はこのように、発生します。

TypeError: 'managedbuffer' object is not callable 

上記PyQt5に適用されるべきであるとPyQt4のほとんどのバージョン(4.3以上)

+0

これは、ラムダによってMyClassとMyModelへの参照を保持することを確認し、明確にしてくれてありがとう!残念なのは、ラムダのガベージコレクションが他の関数オブジェクトと異なる理由です。クラスをインスタンス化し、メソッドの1つを次のように接続するとします。 'model.model_changed_signal.connect(ModelListener()。handle_signal)'、 'ModelListener'のインスタンスはガベージコレクトされ、' handle_signal'は決してされませんと呼ばれる。 – tjalling

+0

@tjalling。ラムダの場合、ガベージコレクションはまったく同じです。その違いは完全にクロージャによるものであり、*任意の*関数がそれらのうちの1つを形成することができます。関数ではなく、余分な参照を保持するのはクロージャです。あなたのコメントに与えられた例ではクロージャはありません。 – ekhumoro

+0

しかし、クロージャーへの参照が必要なのでしょうか?ラムダへの最後の参照を捨てると(例えば 'None'に設定することによって)、ラムダと対応するクロージャーはガベージコレクションされますか?ラムダを 'pyqtSignal'に接続するとラムダ(およびクロージャ)がガーベジコレクションされるのを防ぐのに対して、バインドされた関数をシグナルに接続しても、そのオブジェクトが(その関数がバインドされている)集めました? – tjalling

関連する問題