Декоратор добавляет неожиданный аргумент - PullRequest
2 голосов
/ 29 января 2020

Я хотел использовать декоратор для обработки исключений в моем приложении 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 ]

3 голосов
/ 29 января 2020

Декоратор не проблема; вы просто не определили fun с правильным количеством параметров.

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

fun - обычная функция; self.fun является связанным методом, который при вызове вызывает fun с self в качестве первого аргумента и передает свои собственные аргументы в качестве дополнительных аргументов fun. Что бы ни вызывало self.fun, передается дополнительный аргумент, поэтому определение fun должно принимать это.

3 голосов
/ 29 января 2020

Проблема в том, что QAction.triggered испускает логическое значение при выдаче. Когда слот получает сигнал, аргументы сигнала представляются в сигнатуре слота. Когда слот имеет более короткую сигнатуру, чем сигнал, дополнительные аргументы игнорируются. В вашем случае у функции без декорации нет входных параметров, кроме self, поэтому проверенный аргумент QAction.triggered игнорируется, когда функция без декорации получает сигнал. Однако декорированная функция получает произвольное количество аргументов, поэтому, когда декорированная функция получает сработавший сигнал, проверенный аргумент не игнорируется, что является дополнительным аргументом, на который жалуется Python.

2 голосов
/ 29 января 2020

Проблема вызвана тем, что сигнал 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!")
0 голосов
/ 29 января 2020

Этот аргумент добавляет не декоратор, а тот факт, что вы имеете дело с методом.

Он будет действовать так же, если вы опустите @handle_exceptions.

Что происходит?

  • Вы берете self.fun и передаете его self.menu_action.triggered.connect().
  • Всякий раз, когда срабатывает действие меню, он (предположительно) пытается вызвать данный вызываемый объект с одним аргументом (может быть аргумент события?)
  • Но: когда вы берете это self.fun, вы не получаете сам объект функции, но вы получаете то, что MainWindow.fun.__get__(self) (или что-то подобное, я не помню точный синтаксис) дает вам, так называемый «связанный метод объекта». Это объект-обертка, который можно вызывать и отклонять вызовы исходного объекта функции, но добавляет указанный self к списку аргументов.
    • Это приводит к тому, что этот объект вызывается с одним аргументом (объект события?), В результате чего исходная функция вызывается с двумя аргументами (self и объект события). Поскольку он не готов принять этот дополнительный объект события, вы получите указанную ошибку.
...