user985779
user985779

Reputation: 51

How to use @pyqtSlot decorator with other decorators in QML?

SOLVED: See my answer below. I cannot accept for 2 days.

I have a Python object derived from QObject whose methods I call from QML. These methods are of course decorated with @pyqtSlot. However, there is a problem when I try to combine with my own decorators like so:

@pyqtSlot()
@decorator
def call(self):
    print('Called!')

I get the error file:X.qml:99: TypeError: Property 'call' of object X(0x7fed4c07fa50) is not a function

This is the decorator I am using:

def decorator(f):
    def wrapped(self):
        print('get decorated')
        return f(self)
    return wrapped

Could anyone clarify why this doesn't work? Some behavior of PyQt? Or am I doing something wrong?

NOTE: It will work if the decorator simply returns f, the problems are when the decorator returns a nested function such as wrapped.

Upvotes: 1

Views: 1476

Answers (3)

user985779
user985779

Reputation: 51

Okay, with the help of @eyllanesc's answer I was able to get to the root of the problem: which is that the decorator I was using was not preserving the function signature of my slot.

Simply changing my decorator to this fixes the problem:

def decorator(f):
    def call(self):
        print('get decorated')
        return f(self)
    return wrapped

However, this decorator isn't very reusable because of the hard-coded wrapper function name. The "correct" (only using standard libraries) way to fix this is by using the wraps decorator from functools:

from functools import wraps
def decorator(f):
    @wraps(f)
    def any_name_you_want(self):
        print('get decorated')
        return f(self)
    return any_name_you_want

Upvotes: 0

eyllanesc
eyllanesc

Reputation: 244162

Using the decorator library does not generate problems, in the following part I show an example:

main.py

import sys
from PyQt5 import QtCore, QtGui, QtQml
import decorator


@decorator.decorator
def foo_decorator(f, *args, **kwargs):
    print('get decorated')
    return f(*args, **kwargs)


class Helper(QtCore.QObject):    
    @foo_decorator
    @QtCore.pyqtSlot()
    def call(self):
        print('Called!')


if __name__ == "__main__":

    app = QtGui.QGuiApplication(sys.argv)
    obj = Helper()
    engine = QtQml.QQmlApplicationEngine()
    engine.rootContext().setContextProperty("obj", obj)
    engine.load(QtCore.QUrl.fromLocalFile('main.qml'))
    if not engine.rootObjects():
        sys.exit(-1)
    sys.exit(app.exec_()) 

main.qml

import QtQuick 2.11
import QtQuick.Controls 2.4

ApplicationWindow {
    title: "Hello World"
    width: 640
    height: 480
    visible: true

    Component.onCompleted: obj.call()
}

Output:

get decorated
Called!

Upvotes: 2

S. Nick
S. Nick

Reputation: 13681

Try it:

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui     import *
from PyQt5.QtCore    import *

def decorator(f):
    def wrapped(self):
        print('get decorated')
        return f(self)
    return wrapped

class App(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("PyQt5 button")
        self.setGeometry(500, 100, 320, 200)
        button = QPushButton('  \n   PyQt5\n   button\n  ', self)
        button.setIcon(QIcon("E:/_Qt/img/qt-logo.png"))
        button.setIconSize(QSize(34, 34))
        button.setToolTip('This is an example button')
        button.setFlat(True) 
        button.move(120,70) 
        button.clicked.connect(self.on_click)

    @pyqtSlot()
    @decorator
    def on_click(self):
        print('PyQt5 button click')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    ex.show()
    sys.exit(app.exec_())

enter image description here

Upvotes: 0

Related Questions