Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to change __class__ of PySide.QtGui objects

I've used PyQt4 quite a lot - sometimes I like to overload some of the objects to allow me to add some functionality. This works fine in PyQt4, eg:

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

However, I'm trying to adapt some of my code so that it uses PySide instead of PyQt4. My understanding is that they should have the same functionality. PySide will not allow me to do the same thing.

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

This errors with:

TypeError: __class__ assignment: only for heap types

Is there another way I can change the class of my object to avoid this error? I'm not really sure what's causing it.

NOTE: I need to change the class of the object after it is created as the object is created in a library which is compiled by pyuic. Also my PySide installation does not have the uic module.

like image 749
ninhenzo64 Avatar asked Jan 29 '26 12:01

ninhenzo64


1 Answers

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''
like image 140
Uyghur Lives Matter Avatar answered Jan 31 '26 00:01

Uyghur Lives Matter



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!