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_())
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.