装饰器添加了意外的参数

时间:2020-01-29 15:55:35

标签: python pyqt pyqt5 decorator

我想使用装饰器处理我的PyQt5应用程序中的异常:

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!")

运行时,出现以下异常:

fun() takes 1 positional argument but 2 were given

输出为False(装饰器中的打印参数)。

有趣的是,当我在构造函数中直接由fun()运行self.fun()函数或对装饰程序进行注释时,一切正常。似乎装饰器添加了一个附加参数,但仅在信号调用该函数时才添加。发生了什么事?

4 个答案:

答案 0 :(得分:3)

装饰器不是问题;您只是没有为fun定义正确数量的参数。

@handle_exceptions
def fun(self, foo):
    print("hello there!")

fun是普通函数; self.fun是一个绑定方法,该方法在被调用时以fun作为第一个参数调用self,并将自己的参数作为附加参数传递给fun。凡是叫self.fun的人都会传递 additional 参数,因此fun的定义必须接受。

答案 1 :(得分:3)

问题是QAction.triggered发出时会发出布尔值。当插槽接收到信号时,信号参数将在插槽的签名中提交。当插槽的签名比信号短时,多余的参数将被忽略。在您的情况下,非装饰函数除了self外没有其他输入参数,因此当非装饰函数接收到信号时,QAction.triggered的选中参数将被忽略。但是,修饰函数接收任意数量的参数,因此当修饰函数接收触发信号时,不会忽略选中的参数,这是Python抱怨的额外参数。

答案 2 :(得分:2)

该问题是由于triggered信号过载导致的,也就是说它具有2个签名:

void QAction::triggered(bool checked = false)
QAction.triggered()
QAction.triggered(bool checked)

因此,默认情况下,它发送一个布尔值(false),该布尔值显然不接受导致错误的“ fun”方法。

在这种情况下,解决方案是使用@pyqtSlot()装饰器来指示您必须接受的签名:

@pyqtSlot()
@handle_exceptions
def fun(self):
    print("hello there!")

答案 3 :(得分:0)

不是装饰者添加了这个参数,而是您正在处理方法的事实。

如果省略了@handle_exceptions,它的行为将相同。

会发生什么?

  • 您拿起self.fun并将其传递给self.menu_action.triggered.connect()
  • 每当菜单操作触发时,它(大概)就会尝试使用一个参数(可能是事件参数?)来调用给定的可调用对象
  • 但是:当您使用此self.fun时,您并没有获得函数对象本身,而是得到了MainWindow.fun.__get__(self)(或类似的,我不记得确切的语法)给您的内容,所谓的“绑定方法对象”。它是一个包装对象,可以调用该包装对象,并转移对原始函数对象的调用,但将给定的self附加到参数列表中。
    • 这导致以下事实:使用一个参数(事件对象?)调用此对象会导致使用两个参数(self和事件对象)调用原始函数。由于尚未准备好使用此附加事件对象,因此会出现上述错误。