3

我经常使用 PyQt4 - 有时我喜欢重载一些对象以允许我添加一些功能。这在 PyQt4 中运行良好,例如:

from PyQt4 import QtGui
button = QtGui.QPushButton()
class MyPushButton(QtGui.QPushButton): pass
button.__class__ = MyPushButton

但是,我正在尝试调整我的一些代码,以便它使用 PySide 而不是 PyQt4。我的理解是它们应该具有相同的功能。PySide 不允许我做同样的事情。

from PySide import QtGui
button = QtGui.QPushButton()
class MyPushButton(QtGui.QPushButton): pass
button.__class__ = MyPushButton

这与以下错误有关:

TypeError: __class__ assignment: only for heap types

还有另一种方法可以更改对象的类以避免此错误吗?我不确定是什么原因造成的。

注意:我需要在创建对象后更改对象的类,因为对象是在 pyuic 编译的库中创建的。我的 PySide 安装也没有 uic 模块。

4

2 回答 2

3

PySide 使用Shiboken从其 C++ 类生成 Qt 的 CPython 绑定。所有的 Qt python 类,例如QPushButton都是用 C++ 实现的,这就是为什么你不能覆盖__class__.

>>> button = QPushButton()
>>> button.__class__ = MyButton
TypeError: __class__ assignment: only for heap types

根据 Shiboken 的文档, 只要方法是虚拟的(可覆盖),您就可以对已实例化的对象上的方法进行修补(或打孔):

import types

def override_text(self):
    return 'overridden'

# Bind override_text() to button.
button.text = types.MethodType(override_text, button, QPushButton)

更进一步,您可以将其子类QPushButton化为MyButton,并将方法从动态注入MyButtonQPushButton 实例中。创建MyButton一个子类纯粹是可选的,但是 除了修改的实例之外,QPushButton您还可以创建自己的实例。MyButtonQPushButton

让我们定义MyButton为 的子类QPushButton

class MyButton(QPushButton):

    def text(self):
        # This will override QPushButton's text() method.
        print("inside MyButton.text()")
        return QPushButton.text(self)
  • 注意:您必须使用旧式调用父类方法。 super()以 a 失败,因为注入方法时TypeErrorself 实际上是 aQPushButton 而不是 a 。MyButton

或者,如果您想采用更多的 mixin 方法,让我们定义MyButtonOverrides

class MyButtonOverrides(object):

    def text(self):
        # This will override QPushButton's text() method.
        print("inside MyButtonOverrides.text()")
        return self.__class__.text(self)
  • 注意:您可以直接调用,QPushButton.text()因为self.__class__ 您不会MyButtonOverrides直接使用。

现在让我们定义extend_instance()哪个会将您的覆盖方法从MyButton(或MyButtonOverrides)注入到QPushButton 实例中:

import inspect

def extend_instance(obj, cls):
    for name, attr in vars(cls).items():
        if inspect.isroutine(attr):
            # Bind instance, class and static methods to *obj*.
            setattr(obj, name, attr.__get__(obj, obj.__class__))

如果您希望您的类方法保持绑定到它们的原始类(例如,MyButton),请使用以下内容:

def extend_instance(obj, cls):
    for name, attr in vars(cls).items():
        if inspect.isroutine(attr):
            if isinstance(attr, classmethod):
                # Bind class methods to *cls*.
                setattr(obj, name, attr.__get__(cls, cls))
            else:
                # Bind instance and static methods to *obj*.
                setattr(obj, name, attr.__get__(obj, obj.__class__))

通过我的测试,这适用于 Python 2.7 和 3.3,但应该适用于 2.6+ 和 3+。

最后要修改按钮,使用extend_instance().

>>> button = QPushButton()
>>> extend_instance(button, MyButton)
>>> button.text()
inside MyButton.text()
u''
于 2014-02-20T18:15:43.127 回答
1

猴子补丁__class__是完全没有必要的。相反,您应该在 Qt Designer 中提升相关的小部件,以便它们自动使用您的子类。

为此,在 Qt Designer 中,右键单击小部件并选择“Promote to...”。在对话框中,将“Promoted class name”设置为您的子类(例如“MyPushButton”),并将“Header file”设置为包含子类的模块的python导入路径(例如“myapp”或“myapp.gui”,管他呢)。

现在单击“Add”,然后单击“Promote”,您将在 Object Inspector 窗格中看到类从“QPushButton”更改为“MyPushButton”。

pyuic当您使用or重新生成 ui 模块时pyside-uic,它将包含如下代码:

class Ui_Window(object):
    def setupUi(self, Window):
        ...
        self.button = MyPushButton(Window)
        self.button.setObjectName("button")
        ...

from myapp import MyPushButton

因此,提升按钮将被创建为 的实例MyPushButton,并且不再需要破解__class__

这种提升小部件的技术适用于 PyQt 和 PySide。

于 2014-02-20T21:27:47.060 回答