Decorator adds an unexpected argument

佐手、 提交于 2021-02-05 07:29:24

问题


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 to self.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 what MainWindow.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 given self 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.


来源:https://stackoverflow.com/questions/59970871/decorator-adds-an-unexpected-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!