Unable to change __class__ of PySide.QtGui objects

爷,独闯天下 提交于 2019-12-04 13:45:51

PySide uses Shiboken to generate the CPython bindings for Qt from its C++ classes. All of the Qt python classes such as QPushButton are implemented in C++ which is why you cannot overwrite __class__.

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

According to the Shiboken's documentation, you can monkey patch (or duck punch) the methods on an already instantiated object so long as the methods are virtual (overridable):

import types

def override_text(self):
    return 'overridden'

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

Taking this further you can sub-class QPushButton as MyButton, and dynamically inject the methods from MyButton into the QPushButton instance. Making MyButton a sub-class of QPushButton is purely optional, but would allow you to make your own instances of MyButton in addition to the modified QPushButton instances.

Let's define MyButton as a sub-class of QPushButton.

class MyButton(QPushButton):

    def text(self):
        # This will override QPushButton's text() method.
        print("inside MyButton.text()")
        return QPushButton.text(self)
  • NOTE: You have to use the old-style of calling parent class methods. super() fails with a TypeError because self is actually a QPushButton and not a MyButton when the method is injected.

Or if you wanted to take more of a mixin approach, let's define MyButtonOverrides:

class MyButtonOverrides(object):

    def text(self):
        # This will override QPushButton's text() method.
        print("inside MyButtonOverrides.text()")
        return self.__class__.text(self)
  • NOTE: You can call the QPushButton.text() directly through self.__class__ because you won't be using MyButtonOverrides directly.

Now let's define extend_instance() which will inject your override methods from MyButton (or MyButtonOverrides) into the QPushButton instance:

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__))

If you'd like your class methods to remain bound to their originating class (e.g., MyButton) then use the following:

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__))

Through my testing this works in Python 2.7 and 3.3, but should work on 2.6+ and 3+.

Finally to modify the button, use extend_instance().

>>> button = QPushButton()
>>> extend_instance(button, MyButton)
>>> button.text()
inside MyButton.text()
u''

Monkey-patching __class__ is completely unnecessary. Instead, you should promote the relevant widgets in Qt Designer so that they automatically use your subclass.

To do this, in Qt Designer, right-click the widget and select "Promote to...". In the dialog, set "Promoted class name" to your subclass (e.g. "MyPushButton"), and set "Header file" to the python import path for the module containing the subclass (e.g. "myapp", or "myapp.gui", or whatever).

Now click "Add", and then "Promote", and you will see the class change from "QPushButton" to "MyPushButton" in the Object Inspector pane.

When you re-generate your ui module with pyuic or pyside-uic, it will contain code like this:

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

from myapp import MyPushButton

So the promoted button will be created as an instance of MyPushButton, and there is no longer any need to hack __class__.

This technique of promoting widgets will work for both PyQt and PySide.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!