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.
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__
.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):
Taking this further you can sub-class
QPushButton
asMyButton
, and dynamically inject the methods fromMyButton
into theQPushButton
instance. MakingMyButton
a sub-class ofQPushButton
is purely optional, but would allow you to make your own instances ofMyButton
in addition to the modifiedQPushButton
instances.Let's define
MyButton
as a sub-class ofQPushButton
.super()
fails with aTypeError
because self is actually aQPushButton
and not aMyButton
when the method is injected.Or if you wanted to take more of a mixin approach, let's define
MyButtonOverrides
:QPushButton.text()
directly throughself.__class__
because you won't be usingMyButtonOverrides
directly.Now let's define
extend_instance()
which will inject your override methods fromMyButton
(orMyButtonOverrides
) into theQPushButton
instance:If you'd like your class methods to remain bound to their originating class (e.g.,
MyButton
) then use the following: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()
.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
orpyside-uic
, it will contain code like this: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.