问题
I wanted to use a decorator to handle exceptions in my PyQt5 application:
def handle_exceptions(func):
def func_wrapper(*args, **kwargs):
try:
print(args)
return func(*args, **kwargs)
except Exception as e:
print(e)
return None
return func_wrapper
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("main_window.ui",self)
self.connect_signals()
def connect_signals(self):
self.menu_action.triggered.connect(self.fun)
@handle_exceptions
def fun(self):
print("hello there!")
When I run I get the following exception:
fun() takes 1 positional argument but 2 were given
The output is False
(printed args in the decorator).
The interesting thing is that when I run the fun()
function directly by self.fun()
in the constructor or comment the decorator, everything works. Seems like the decorator adds an additional argument, but only when the function is called by the signal. What is going on?
回答1:
The problem is caused because the triggered signal is overload, that is to say it has 2 signatures:
void QAction::triggered(bool checked = false)
QAction.triggered()
QAction.triggered(bool checked)
So by default it sends a boolean(false) that clearly does not accept the "fun" method causing the error.
In this case the solution is to use the @pyqtSlot() decorator to indicate the signature that you must accept:
@pyqtSlot()
@handle_exceptions
def fun(self):
print("hello there!")
回答2:
The decorator isn't the problem; you simply haven't defined fun
with the correct number of parameters.
@handle_exceptions
def fun(self, foo):
print("hello there!")
fun
is an ordinary function; self.fun
is a bound method that, when called, calls fun
with self
as the first argument and passing its own arguments as additional arguments to fun
. Whatever is calling self.fun
is passing an additional argument, so the definition of fun
has to be accept that.
回答3:
The issue is that QAction.triggered
emits a boolean when emitted. When a slot receives a signal, the arguments of the signal are submitted in the signature of the slot. When the slot has a shorter signature than the signal the extra arguments are ignored. In your case, the non-decorated function has no input parameters besides self, so the checked argument of QAction.triggered
is ignored when the non-decorated function receives the signal. However, the decorated function receives an arbitrary number of arguments, so when the decorated function receives the triggered signal, the checked argument is not ignored which is the extra argument that Python is complaining about.
回答4:
It's not the decorator which adds this argument, it is the fact that you are dealing with a method.
It would act the same way if you omitted the @handle_exceptions
.
What happens?
- You take
self.fun
and pass it toself.menu_action.triggered.connect()
. - Whenever a menu action triggers, it (presumably) tries to call the given callable with one argument (maybe an event argument?)
- But: When you take this
self.fun
, you don't get the function object itself, but you get whatMainWindow.fun.__get__(self)
(or alike, I don't remember the exact syntax) gives you, a so-called "bound method object". It is a wrapper object which can be called and deflects the calls to the original function object, but prepends the givenself
to the argument list.- This leads to the fact that this object being called with one argument (the event object?) results in the original function being called with two arguments (
self
and the event object). As it is not ready to take this additional event object, you get the said error.
- This leads to the fact that this object being called with one argument (the event object?) results in the original function being called with two arguments (
来源:https://stackoverflow.com/questions/59970871/decorator-adds-an-unexpected-argument