'PyQt5: Using decorators with methods connected to signals

I want to use a decorator to handle exceptions within a class. However, when using the decorator, it always gives me:

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

I am quite sure it occurs due to self, because it works perfectly outside the class. But I cannot manage to use it inside the class. Can someone help me?

Here is a 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_())


Solution 1:[1]

The error occurs because the clicked signal sends a default checked parameter which the f1 method does not provide an argument for. There are several ways to work around this:

  • change the signature to allow for the extra argument:

     def f1(self, checked=False):
    
  • use a lambda to consume the unwanted argument:

     b1.clicked.connect(lambda: self.f1())
    
  • wrap the method as a pyqt slot:

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

The reason why this last one works is because signals with default arguments are treated as two separate overloads: one which which sends the parameter, and one which doesn't. The slot decorator therefore allows you to explicitly select which one is required (the other overload being @QtCore.pyqtSlot(bool)).

Another peculiarity specific to PyQt is that signals connected to undecorated slots will normally ignore unused parameters. The lambda solution above takes advantage of this mechanism, but your report_exceptions decorator effectively bypasses it, which is why you get the TypeError.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1