Reputation: 51
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
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
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
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_())
Upvotes: 0