結論
PyQtのconnect()の引数にはlambdaを使うべき。特に、QtCore.pyqtSlot()をdecorateしている場合。
この記事はPyQtのsignalとconnectとlambda式についてを補強する情報として、なぜconnect()の引数のlambda式を使うべきなのかの具体例を例示しています。
はじめに
騙されたと思って以下のコードをコピペして実行してみてください。
import sys
from PyQt5 import QtCore
from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QApplication, QWidget
def Error_Handling(func):
def Try_Function(*args, **kwargs):
try:
func(*args, **kwargs)
except TypeError as e:
print(f"{func.__name__} wrong data types. ", e)
except Exception as e:
print(f"{func.__name__} has any Error. ", e)
return Try_Function
class Window(QWidget):
def __init__(self):
super().__init__()
b1 = QPushButton('1-f1')
b1.clicked.connect(self.f1)
b2 = QPushButton('2-f2')
b2.clicked.connect(self.f2)
b3 = QPushButton('3-f1(lambda)')
b3.clicked.connect(lambda: self.f1())
b4 = QPushButton('4-f2(lambda)')
b4.clicked.connect(lambda: self.f2())
layout = QVBoxLayout(self)
layout.addWidget(b1)
layout.addWidget(b2)
layout.addWidget(b3)
layout.addWidget(b4)
self.setLayout(layout)
@QtCore.pyqtSlot()
@Error_Handling
def f1(self):
print(self.sender().text())
return self.name
@QtCore.pyqtSlot()
@Error_Handling
def f2(self):
print(self.sender().text())
return self.name
app = QApplication([])
window = Window()
window.show()
window.raise_()
sys.exit(app.exec_())
ボタンを上から下に押していった出力が以下の通り。
1-f1
f1 has any Error. 'Window' object has no attribute 'name'
2-f2
f1 has any Error. 'Window' object has no attribute 'name'
3-f1(lambda)
f1 has any Error. 'Window' object has no attribute 'name'
4-f2(lambda)
f2 has any Error. 'Window' object has no attribute 'name'
おかしいのは、"2-f2"を押した際の挙動。"f2"をconnectしたはずの"2-f2"を押しているのに、なぜか"f1"が実行されている。
解決策
上記の"2-f2"の挙動を解決するには、lambda式を使えばよい。
原因の推測
公式のドキュメントをろくに読んでいないので、(見つかるのはこれぐらいで"PyQt5"のドキュメントではない)、あくまで予想です。
挙動から推測すると、Class Objectの生成時のconnect()にQtCore.pyqtSlot()でdecorateされた関数が引数として与えられたときに、"1-f1"ではQtCore.pyqtSlot()()というlambda式で構成された関数が定義され、その引数のDefault値が"f1"として作られる。その後、"2-f2"でも同様にQtCore.pyqtSlot()()というlambda式で構成された関数が定義されるが、そのときに引数のDefault値が"f1"のままになっている。なぜDefault値が"f1"のままかというと、QtCore.pyqtSlot()が引数なしで呼び出され結果、最初に与えられた引数がそのまま使いまわされたのではないか?と邪推しています。connect()の引数をlambda式で与えれば、event発生時にlambda式として評価されるまで、入れ子状の関数の列として維持されるため、正しい関数が呼び出される。
どうも、QtのSlotとPythonを組み合わせた結果として得られる挙動に見えるためドキュメントを調査したいのだが、あるのはQtのドキュメントのみで、PyQt5のドキュメントがなかなか見つからない。。。