PyQt5:将装饰器与连接到信号的方法一起使用

PyQt5: Using decorators with methods connected to signals

我想使用装饰器来处理 class 中的异常。但是,在使用装饰器时,它总是给我:

TypeError: f1() takes 1 positional argument but 2 were given

我很确定它是由于 self 而发生的,因为它在 class 之外工作得很好。但是我无法在 class 中使用它。有人可以帮助我吗?

这是一个 MWE:

from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QApplication, QWidget
import sys


def report_exceptions(f):
    def wrapped_f(*args, **kwargs):
        try:
            f(*args, **kwargs)
        except Exception as e:
            print(f"caught: {e}")   
    return wrapped_f


class Window(QWidget):
    def __init__(self):
        super().__init__()
        b1 = QPushButton('1')
        b1.clicked.connect(self.f1)
        layout = QVBoxLayout(self)
        layout.addWidget(b1)
        self.setLayout(layout)

    @report_exceptions
    def f1(self):
        raise Exception("Error inside f1")


app = QApplication([])
window = Window()
window.show()

sys.exit(app.exec_())

错误发生是因为 clicked signal 发送默认的 checked 参数,而 f1 方法没有为其提供参数。有几种方法可以解决此问题:

  • 更改签名以允许额外参数:

     def f1(self, checked=False):
    
  • 使用 lambda 来处理不需要的参数:

     b1.clicked.connect(lambda: self.f1())
    
  • 将方法包装为 pyqt slot:

     @QtCore.pyqtSlot()
     @report_exceptions
     def f1(self):
    

最后一个有效的原因是因为带有默认参数的信号被视为两个单独的重载:一个发送参数,一个不发送参数。因此,插槽装饰器允许您显式 select 需要哪一个(另一个重载是 @QtCore.pyqtSlot(bool))。

PyQt 的另一个特点是连接到未修饰槽的信号通常会忽略未使用的参数。上面的 lambda 解决方案利用了这种机制,但是你的 report_exceptions 装饰器有效地绕过了它,这就是你得到 TypeError 的原因。