如何在 QML 中将 @pyqtSlot 装饰器与其他装饰器一起使用?

Posted

技术标签:

【中文标题】如何在 QML 中将 @pyqtSlot 装饰器与其他装饰器一起使用?【英文标题】:How to use @pyqtSlot decorator with other decorators in QML? 【发布时间】:2018-10-11 19:04:59 【问题描述】:

已解决:请参阅下面的答案。我不能接受 2 天。

我有一个派生自 QObject 的 Python 对象,我从 QML 调用它的方法。这些方法当然是用 @pyqtSlot 修饰的。但是,当我尝试像这样与自己的装饰器结合使用时会出现问题:

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

我收到错误file:X.qml:99: TypeError: Property 'call' of object X(0x7fed4c07fa50) is not a function

这是我正在使用的装饰器:

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

谁能澄清为什么这不起作用? PyQt 的一些行为?还是我做错了什么?

注意:如果装饰器只返回f,它会起作用,问题是装饰器返回嵌套函数时,例如 wrapped.

【问题讨论】:

return f () 更改为return f (self) 抱歉,打错了。我得到与return f(self) 相同的结果 @eyllanesc 是的,但我真的很想知道为什么?我不想将外部库用于基本语言功能。 【参考方案1】:

使用decorator库不会产生问题,下面我举个例子:

ma​​in.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_()) 

ma​​in.qml

import QtQuick 2.11
import QtQuick.Controls 2.4

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

    Component.onCompleted: obj.call()

输出:

get decorated
Called!

【讨论】:

是的!这行得通……但是为什么呢?我不希望将外部库用于基本语言功能。 @user985779 这个库很轻量,如果你不想把它当成依赖只需要下载下面的文件:raw.githubusercontent.com/micheles/decorator/master/src/…【参考方案2】:

试试看:

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_())

【讨论】:

Python 不会产生这个问题,而是来自 QML。 我的情况不同。我正在使用 QQmlApplicationEngine 并直接从 QML 调用 Python @pyqtSlot。【参考方案3】:

好的,在@eyllanesc 的回答的帮助下,我能够找到问题的根源:我使用的装饰器没有保留我的插槽的函数签名。

只需将我的装饰器更改为此即可解决问题:

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

但是,由于硬编码的包装函数名称,这个装饰器不是很可重用。解决此问题的“正确”(仅使用标准库)方法是使用来自functoolswraps 装饰器:

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

【讨论】:

以上是关于如何在 QML 中将 @pyqtSlot 装饰器与其他装饰器一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

尽管有 pyqtSlot 装饰器,但 Slot 被调用了两次

如何在 python 中创建路径并将其显示在 qml 画布上

如何将这两个类装饰器与 functools.update_wrapper 一起使用?

如何让 MobX 装饰器与 Create-React-App v2 一起工作?

装饰器与继承

打字稿装饰器与类继承