PyQt und Pyside mögen sich nicht?

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

auf meinem Laptop habe ich sowohl PyQt4 als auch Pyside installiert. Bisher benutze ich PyQt4. Aber nach mehren Recherchen dachte ich mir "Warum benutzt du nicht Pyside?" und wollte einen kleinen Versuch starten. Hier der Quelltext:

Code: Alles auswählen

import sys
from PySide import QtCore, QtGui, QtUiTools

from xarphus.gui import ui_rc

class MDI_Window(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)

        UI_PATH = QtCore.QFile(":/ui_file/mdi.ui")

        try:
            UI_PATH.open(QtCore.QIODevice.ReadOnly)
            ui = QtUiTools.QUiLoader()
            self.ui_mdi = ui.load(UI_PATH, self)
            UI_PATH.close()
        except Exception as ex:
            print "exe", ex

app = QtGui.QApplication(sys.argv)
window = MDI_Window()
window.show()
sys.exit(app.exec_())
Wenn ich das Programm ausführen will, bekomme ich diese Meldung - keinerlei Tracback-Meldungen.
Bild

Heißt das nun, dass PySide will, dass ich PyQt runterwerfen muss? Das wäre mächtig blöd. Ich nahm an, dass ich beide Wrapper behalten kann.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Sophus hat geschrieben:Heißt das nun, dass PySide will, dass ich PyQt runterwerfen muss? Das wäre mächtig blöd. Ich nahm an, dass ich beide Wrapper behalten kann.
Nein, da steht nur, dass der Interpreter nicht mehr funktioniert. Vermutlich sind Dein Interpreter und die Pyside-Version nicht kompatibel. Oder Du fängst mal wieder einen Fehler sinnloserweise ab, ohne ihn korrekt zu behandeln, was im weiteren Programmverlauf zum gezeigten Verhalten führt.

Auch solltest Du den nackten Exceptionhandler um allen Quelltext legen, sonst kann es passieren, dass doch mal eine dieser störenden Exceptions auftritt und nicht Deine schöne print-Ausgabe kommt. Aber das weisst Du ja alles bereits, da Du das hier schon oft lesen konntest. Aus irgendeinem Grund schaffts das leider nicht in Deinen Code, kA warum. Vielleicht hilft ein Editorwechsel ;)
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Erwischt 8) Ich hatte beim schreiben dieses Beispiel-Quelltextes einfach nicht mehr daran gedacht, die Fehlerbehandlung auszuklammern. Blöde Angewohnheit - schwer los zu werde. Ich habe diesmal den Try/Except-Block komplett entfernt. Auch hier kein Traceback, und das gleiche Fenster. Ich werde mal die Versionen miteinander überprüfen.

Aber dann drängt sich bei mir die Frage auf: Wenn ich PySide über pip installiere, kümmert sich pip nicht darum, dass ich stets kompatible Bibliotheken bekomme?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

UPDATE: Also, ich war eben selbst auf der PyPi-PySide-Seite. Wenn man ewig weit nach unten scrollt, kann man auch die *.whl-Datei herunterladen. Ich habe zunächst PySide über pip deinstalliert, und anschließend habe ich die Wheel-Datei (PySide-1.2.4-cp27-none-win32.whl) installiert. Also Kompatible sollte PySide jetzt schon sein, nicht? :shock: Jedoch taucht das Problem weiterhin auf, dass der Interpreter sich aufhängt.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Würde das mal in 'nem Debugger starten und schauen wo es crasht. Vermutung: Es crasht irgendwo in Qt.
the more they change the more they stay the same
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Debugger? Du meinst die ganzen Print-Anweisungen? Ich denke, bei Qt 4.8.4 sollte es doch nicht krachen? Also PyQt hat damit keine Probleme.
Zuletzt geändert von Sophus am Sonntag 21. Februar 2016, 00:16, insgesamt 1-mal geändert.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Nein, einen Debugger. VS hat einen Debugger, gdb ist ein Bekannter, WinDbg, ...
the more they change the more they stay the same
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ich habe mal den Debugger von PyCharm benutzt, da ich mit PyCharm arbeite. Das gleiche Verhalten. Beim Debuggen kommt enefalls das gleiche Melde-Felnster, und nach dem Klick auf X steht folgendes in der PyCharm-Konsole:
C:\Python27\python.exe "C:\Program Files (x86)\JetBrains\PyCharm Community Edition 3.4.1\helpers\pydev\pydevd.py" --multiproc --client 127.0.0.1 --port 54182 --file D:/Dan/Python/xarphus_project/test_the_pyside.py
pydev debugger: process 9808 is connecting

Connected to pydev debugger (build 135.1057)

Process finished with exit code -1073741819 (0xC0000005)
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Nicht den Python Code debuggen, sondern den Interpreter. Der Interpreter crasht an deinem Code, das sollte nicht passieren, d.h. i-was läuft komplett falsch, dass der Interpreter am Code crasht an sich crasht ist unwahrscheinlich -> Vermutung: Qt crasht, was dazu führt, dass der Interpreter abschmiert. Deshalb musst du nicht den Python Code debuggen sondern den Interpreter selber, mit z.B. VS, WinDbg, OllyDbg, ImmunityDbg, Ida, gdb, ... Mit dem Debugger kannst du dann sehen wo der Crash passiert (ohne Debug-Symbols siehst du nur grob wo, aber das sollte erstmal reichen).
the more they change the more they stay the same
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Nun, ich dachte, ein radikaler Schnitt wäre eher angesagt. Was tat ich? Ich habe zunächst Python 2.7.6 und Qt deinstalliert. Anschließend habe ich mir die Python(x,y)-2.7.10.0.exe heruntergeladen. Das Schöne an diesem Paket ist, das schon vieles (auch etwas überladen) dabei ist. Man hat bei der Installation schon vieles in einem Rutsch erledigt. In diesem Paket ist auch Qt 8.4.6 gleich dabei. Danach habe ich PySide über pip installiert. Mein erster Test mit dem folgenden Quelltext war dann erfolgreich. PySide scheint zu funktionieren - rchtig gelesen, es scheint. Der nachfolgende Quelltext funktioniert einwandfrei.

Code: Alles auswählen

import sys
from PySide import QtGui

app = QtGui.QApplication(sys.argv)

win = QtGui.QWidget()

win.resize(320, 240)
win.setWindowTitle("Hello, World!")
win.show()

sys.exit(app.exec_())
Bevor ich dazu überging die ui-Dateien dynamisch zu laden habe ich mittels pyside-rcc.exe die Ressource-Datei in eine *.py-Datei umgewandelt und anschließend habe ich die *.py-Datei importiert und versucht dynamisch zu laden. Und auch hier wieder der gleiche Fehler. Der Python-Interpreter baumelt sich auf. Ich muss dann den Interpreter radikal beenden.

Und das obwohl ich zuvor alles deinstalliert habe: Qt, Python.. einfach alles was damit zutun hatte. Und nach einer kompletten Neuinstallation scheint PySide immer noch ein Problem zu haben. Merkwürdig ist allerdings, dass im obigen Quelltext keine Probleme aufkommen, aber beim dynamischen Laden schon? Das ist echt zum Heulen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Nach weiterem Suchen fand ich diese Dokumentation für PySide 1.2.4. Auf der Seite 8 der Dokumentation werden einige Programme aufgeführt, die für PySide benötigt werden. Und an einer Stelle bin ich gestolpert. Dort steht folgendes:
Note:
• If you are using the Windows SDK versions linked above, make sure that you have .NET Framework Version
4.0 installed on your system. Version 4.5 will not work.
So mutig wie ich war, habe ich mein aktuelles .NET Framework 4.5.2 deinstalliert und natürlich die alte Version 4.0 installiert. Nach dem ich alles beachtet habe, will es immer noch nicht klappen.

Halten wir also fest. Entweder ich stelle mich mächtig bescheuert an, oder aber PyQt ist in der Tat pflegeleichter? Im Grunde hätte ich auch nichts gegen diesen Wrapper, aber ich hatte gehofft, dass der Umstieg auf PySide ein Klacks wäre.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Wenn ich mich recht entsinne, funktionierte das dynamische ui-Laden lange Zeit nicht mit PySide. Da ich PySide nicht einsetze, kann ich Dir auch nicht sagen, ob das jemals zufriedenstellend gelöst wurde.
Könntest Du mal ein minimales nicht mehr lauffähiges Beispiel erstellen? Mit minimal meine ich sowohl abgespeckten Pythoncode als auch eine minimale ui-Datei (mit einem QWidget oder so). Auch wäre wichtig zu wissen, ob das Minimalbeispiel mit kompilierter ui-Datei wiederum funktioniert. Meine Vermutung geht in Richtung QUiLoader, vermutlich fehlen Qt in PySide dll-Symbole oder PySide versucht im Loader welche doppelt zu laden. Sowas lässt sich dann mit einem Debugger rausfinden.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Der Einfachheit-halber habe ich das Minimal-Programm-Beispiel in eine Zip-Datei gepackt und hochgeladen. Hier könnt ihr das das Beispiel herunterladen.

In diesem Archiv sind folgende Dateien:
- CustomDialog.ui
- main_test_pyside.py
- ui.qrc
- ui_rc.py

Sowohl als Kompilat als auch als reine '.ui-Datei funktionieren bei mir nicht. Das heißt, die kompilierte Variante klappt nicht, als auch das Laden der reinen '.ui-Datei. Was ich allerdings noch nicht probiert habe, ist, die *.ui-Datei in ein Python-Code umzuwandeln, und es dann zu laden. Erstens handhabe ich das bei PyQt4 nicht so, und zweitens dachte ich, müsste es auch ohne Übersetzung gehen.

Hinweis: In main_test_pyside.py habe ich weiter unten ein kleines Schnipsel auskommentiert. Dieser Schnipsel allein funktioniert anstandslos. Was also bedeutet, das PySide und Qt auf meinem Laptop ordnungsgemäß installiert sind. Wäre ja auch ein dickes Ding, denn PyQt4 hätte sich dann schon gemeldet, wenn irgendetwas mit Qt nicht stimmen würde.

P.S. Ich habe PySide 1.2.4 installiert, und benutze Ot 4.8.6 und Python 2.7.10 sowie bis dato PyQt4.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Deinen Code habe ich noch nicht ausprobiert, allerdings funktioniert bei mir das Laden von ui-Dateien in Ubuntu mit Python 2.7 und PySide problemlos. Dabei ist mir eine andere Sache aufgefallen, nämlich die Art und Weise wie PySide die ui-Dateien lädt, unterscheidet sich sehr stark von PyQt. Da könntest Du Probleme kriegen, dass halbwegs ohne viel Bloatcode einheitlich abzubilden.

Könntest Du bitte mal probieren, ob folgendes bei Dir läuft (als ui-Datei einfach mal ein QWidget im Designer mit Inhalt erstellen):

Code: Alles auswählen

import pysideuic
import xml.etree.ElementTree as xml
from cStringIO import StringIO

def loadUiType(filename):
    """Load form class from ui file."""
    parsed = xml.parse(filename)
    widget_class = parsed.find('widget').get('class')
    form_class = parsed.find('class').text
    with open(filename, 'r') as f:
        o = StringIO()
        frame = {}
        pysideuic.compileUi(f, o, indent=0)
        pyc = compile(o.getvalue(), '<string>', 'exec')
        exec pyc in frame
        form_class = frame['Ui_%s'%form_class]
        base_class = eval('QtGui.%s'%widget_class)
    return form_class, base_class

def loadUi(filename, obj):
    """
    Load ui form class onto a given QWidget object.

    NOTE:
    This implements only the most common usage of PyQt's uic.loadUi as
    done in `__init__` with `uic.loadUi('/some/ui/file', self)` meaning
    the initializing class gets decorated with the form class from the
    ui file itself.
    Feel free to implement missing API features like a return value.
    """
    form_cls, base_cls = loadUiType(filename)
    cls = obj.__class__
    obj.__class__ = cls.__class__(
        cls.__name__ + form_cls.__name__, (cls, form_cls), {})
    obj.setupUi(obj)


from PySide import QtGui, QtUiTools, QtCore

class TestWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)
        loadUi('./test.ui', self)

if __name__ == '__main__':
    app = QtGui.QApplication([])
    w = TestWidget()
    print type(w)
    w.show()
    app.exec_()
Das ist ein Versuch, das Laden der ui-Dateien in PySide analog zu PyQt vorzunehmen. Hierzu ahmt die Funktion `loadUi` die grundlegende Funktionalität von PyQts `uic.loadUi` nach.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Sophus hat geschrieben:Sowohl als Kompilat als auch als reine '.ui-Datei funktionieren bei mir nicht. Das heißt, die kompilierte Variante klappt nicht, als auch das Laden der reinen '.ui-Datei.
Und was genau klappt da jetzt nicht? Stürzt das immer noch ab? Bei mir läuft es, zeigt nur nichts an, weil Du das importierte Widget nicht einhängst.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Dein oben genannter Quelltext funktioniert. Da ich den Quelltext nicht verstehe, kannst du mir mal bitte erklären, was du da genau gemacht hast? Kurz zu meinem Quelltext. Was meinst du, das importierte Widget (Label und pushButton) wird nicht eingehängt? Die Widgets, die in der ui-Datei sind? Die werden doch mit geladen, sobald man die ui-Datei lädt?
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Sophus hat geschrieben:Was meinst du, das importierte Widget (Label und pushButton) wird nicht eingehängt? Die Widgets, die in der ui-Datei sind? Die werden doch mit geladen, sobald man die ui-Datei lädt?
Das ist das, was ich meinte - QuiLoader funktioniert sehr anders als PyQts `uic.loadUi`. Damit die Widgets aus der ui-Datei auch gerendert werden, ist mehr nötig (Bsp. aus PySide Doku):

Code: Alles auswählen

class MyWidget(QWidget):
    def __init__(self, parent):
        super(QWidget, self).__init__(parent)
        loader = QUiLoader()
        file = QFile(":/forms/myform.ui")
        file.open(QFile.ReadOnly)
        myWidget = loader.load(file, self)
        file.close()

        layout = QVBoxLayout()
        layout.addWidget(myWidget)
        self.setLayout(layout)
Wie Du siehst, können mit QUiLoader nur Subwidgets erstellt werden, welche zusätzlich in das Elternwidget eingehängt werden müssen. Das ist für Dich ein Riesenproblem, wenn Du transparent zwischen PyQt und PySide wechseln können möchtest, da Du alle Methoden (signal und slots) umbiegen müsstest.

Mit dem von mir gezeigten Ladecode kannst Du das Problem umgehen, da dieser recht ähnlich zu PyQt tickt. Ich hab diesen nochmal etwas aufgeräumt und ein paar Tests dazu geschrieben:

Code: Alles auswählen

from PyQt4 import QtGui, QtCore
from PyQt4.uic import loadUi, loadUiType
#from PySide import QtGui, QtCore
#from uic_fix import loadUi, loadUiType


FormClass, BaseClass = loadUiType('./test.ui')

class TestBase(QtGui.QWidget):
    """Base test class with one slot"""
    def testSlot(self):
        print 'button pressed'


class Test1(TestBase):
    def __init__(self, parent=None):
        TestBase.__init__(self, parent)
        assert(loadUi('./test.ui', self) == self)

    def testSlot(self):
        print 'button pressed'

def test1(app):
    print 'TEST 1: applying form class to initializing class with loadUi'
    w = Test1()
    w.show()
    app.exec_()


class Test2(TestBase, FormClass):
    def __init__(self, parent=None):
        TestBase.__init__(self, parent)
        self.setupUi(self)

    def testSlot(self):
        print 'button pressed'

def test2(app):
    print 'TEST 2: multi inheritance with form class'
    w = Test2()
    w.show()
    app.exec_()


class Test3(TestBase):
    def __init__(self, parent=None):
        TestBase.__init__(self, parent)
        self.ui = FormClass()
        self.ui.setupUi(self)

    def testSlot(self):
        print 'button pressed'

def test3(app):
    print 'TEST 3: explicit form class instantiation (C++ default way)'
    w = Test3()
    w.show()
    app.exec_()


def test4(app):
    print 'TEST 4: late styling with form class'
    w = TestBase()
    f = FormClass()
    f.setupUi(w)
    w.show()
    app.exec_()


def test5(app):
    print 'TEST 5: late styling with loadUi'
    w = TestBase()
    assert(loadUi('./test.ui', w) == w)
    w.show()
    app.exec_()


def test6(app):
    print 'TEST 6: loadUi without baseinstance'
    try:
        loadUi('./test.ui')
    except AttributeError as e:
        assert(e.message == "'QWidget' object has no attribute 'testSlot'")


if __name__ == '__main__':
    app = QtGui.QApplication([])
    test1(app)
    test2(app)
    test3(app)
    test4(app)
    test5(app)
    test6(app)
Wie Du siehst, sind die API-SChnittstellen quasi gleich, für den Wechsel zwischen PySide und PyQt musst Du nur die Importe ändern. PySide braucht hierfür zusätzlich die Datei uic_fix.py:

Code: Alles auswählen

import pysideuic
from PySide import QtGui, QtUiTools
import xml.etree.ElementTree as xml
from cStringIO import StringIO

def loadUiType(filename):
    """Load form class from ui file."""
    parsed = xml.parse(filename)
    widget_classname = parsed.find('widget').get('class')
    form_classname = parsed.find('class').text
    with open(filename, 'r') as f:
        o = StringIO()
        frame = {}
        pysideuic.compileUi(f, o, indent=0)
        pyc = compile(o.getvalue(), '<string>', 'exec')
        exec pyc in frame
        form_class = frame['Ui_%s'%form_classname]
        base_class = getattr(QtGui, widget_classname)
    return form_class, base_class

def loadUi(filename, instance=None):
    """Load ui form class onto a given QWidget object."""
    form_cls, base_cls = loadUiType(filename)
    if not instance:
        instance = type(base_cls.__name__, (base_cls,),{})()
    cls = instance.__class__
    instance.__class__ = cls.__class__(cls.__name__, (cls, form_cls), {})
    instance.setupUi(instance)
    return instance
Zur Erklärung: PyQt kennt QUiLoader nicht. Das kommt direkt aus Qt und ist als Lademechanismus für Formularteile gedacht, wird allerdings selten genutzt in C++. Auch unterliegt es bestimmten Beschränkung - so ist es nicht möglich, den Formularstyle auf eine bestehendes Objekt anzuwenden. Genau das aber macht PyQt mit `uic.loadUi`.
Zum Nachladen der ui-Dateien nutzt PyQt die Fähigkeit von Python, zur Laufzeit Klassen erstellen, Code nachladen oder gar existierende Objekte modifizieren zu können. Und genau das machen die beiden Funktionen `loadUiType` und `loadUi`:
- `loadUiType` lädt eine ui-Datei, holt sich die Klassennamen des Formulars und der Basisklasse, generiert Pythoncode daraus und führt diesen aus, um an die Formularklasse als Pythontypen zu kommen (mit compile :twisted: )
- `loadUi` erweitert die Elternklassen des übergebenen Objektes zur Laufzeit (!) um die Formularklasse und ruft `setupUi` auf, was das Layout anwendet und Signal-/Slotverbindungen aktiviert. Das geht so nicht in C++, weshalb QUiLoader das nicht kann.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Selbst das Einhängen der Widget klappt bei mir nicht. Auch da hängt sich mein Interpreter auf. Einzig und allein dein genannter Quelltext klappt. Da wird die '.ui-Datei ordnungsgemäß geladen. Kommt es mir nur so vor, oder ist PySide etwas anspruchsvoller bzw. umständlicher als PyQt? Denn mit PyQt habe ich quasi gleich sofort angefangen, als ich mich mit Python auseinander gesetzt habe. Denn ich habe mir am Anfang noch nicht die Frage gestellt, ob PySide oder PyQt. Da ich mit PyQt von Anfang an gelernt habe, kommt mit diese Bindung "normal" vor. Nun dachte ich (da PySide und PyQt den gleichen Rahmenwerk benutzen), ich könnte mal mit PySide probieren. Aber irgendwie fühle ich mich leicht verschreckt, von PySide :shock:
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Sophus:
Hab grad rausgefunden, dass PySide ein Segfault beim Beenden des Testskriptes wirft, wenn Test6 der letzte ist und in einem vorherigen Test der Slot `testSlot` angesprochen wurde. Offensichtlich hat PySide Probleme, dass QApplication-Objekt ordentlich abzuräumen, wenn eine Signal-Slotverbindung fehlgeschlagen ist. Ich vermute einen hängenden Zeiger in der Slotlogik, leider hab ich keine Debugsymbole in PySide um der Sache weiter auf den Grund gehen zu können. Evtl. ist das auch die Ursache Deines Segfaults.

Ich glaube nicht, dass PySide per se schwieriger ist. Es ist einfach anders, IMHO ist PySide näher an Qt dran, was die Umsetzung von Qt-Klassen angeht, während PyQt vielfach mit Pythonmitteln abkürzt. Auf der anderen Seite ist PySide vermutlich weniger gut getestet und jünger als PyQt, was den ein oder anderen Segfault erklären könnte.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Es war ein Problem des Lifecycles der Formularklassen-Objekte in Python, wenn die Referenz hierauf bis Programmende voegehalten wird, verschwindet der Segfault. Ich werde das Skript updaten und in ein Repo packen.
Antworten