Signal-Slot-Verbindung: kein Aufruf

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
EdgarButan
User
Beiträge: 7
Registriert: Mittwoch 15. September 2010, 17:56

Ich weiß, dass allein diese Überschrift schon große Unlust hervorrufen wird, an diesem Thread teilzuhaben.
Ich verzweifle allerdings an einem bestimmten Fall in einem meiner Programme, in dem ich ein Signal erfolgreich mit einem Slot verbunden habe, dieser aber ums Verrecken nicht ausgeführt wird.

Der Aufbau:
Ich habe eine Datenbank, aus der ich mittels einer Klasse „DatenEngine“ Daten auslese, modifiziere, lösche.

Code: Alles auswählen

class DatenEngine(QtCore.QObject):
  def hans(self):
    print "hans"
Diese werden in einer QGraphicsView dargestellt.
Bei Beendigung des Programms muss das Programm geänderte Daten in die Datenbank schreiben, hierfür benutze ich das Signal „aboutToQuit“.
Der Verbindungsaufruf sieht folgendermaßen aus:

Code: Alles auswählen

app = QApplication(sys.argv)
engine = DatenEngine(db)
app.aboutToQuit.connect(engine.hans)
und wird außerhalb einer Klasse direkt vor der Eventloop durchgeführt; „engine“ ist eine Instanz von DatenEngine, „hans“ ist hier eine vereinfachte Test-Methode.

Bei diesem connect-Aufruf bekomme ich keinerlei Fehlermeldung. Bis vor wenigen Stunden hatte ich diesen Aufruf noch im alten Stil gestaltet:

Code: Alles auswählen

QtCore.QObject.connect(app, QtCore.SIGNAL("aboutToQuit()"), engine.hans)
Ich bekam keine Fehlermeldung, der connect-Aufruf gab „True“ zurück.

Wenn ich den Aufruf folgendermaßen durchführte:

Code: Alles auswählen

QtCore.QObject.connect(app, QtCore.SIGNAL("aboutToQuit()"), engine, QtCore.SIGNAL("hans()"))
was, wie ich seit einigen Stunden weiß, verpönt ist, bekam ich eine Fehlermeldung, deren Ursache ich leider nicht identifizieren konnte:

Code: Alles auswählen

Traceback (most recent call last):
  File "zellen.py", line 66, in <module>
    print QtCore.QObject.connect(app, QtCore.SIGNAL("aboutToQuit()"), engine, QtCore.SLOT("hans()"))
RuntimeError: underlying C/C++ object has been deleted
Klingt, als hätte ich „engine“ irgendwo verloren.
Direkt vor oder nach dem connect-Aufruf kann ich aber ohne Probleme direkt auf „engine.hans()“ zugreifen.

Wenn ich über das Signal einen Slot in einer anderen Klasse, zum Beispiel der QGraphicsScene, ansteuere und in diesem engine.hans() wiederum direkt aufrufe, wird diese Methode ausgeführt. Das wäre also ein Workaround (allerdings natürlich ein für mich völlig inakzeptabler. Das ist schlecht aufgebaut (nicht, dass der Rest meines Programms gut aufgebaut wäre...) und außerdem nur ein Kaschieren, keine Lösung).

Soweit das Technische. Jetzt wird es Esoterisch:
Ich würde auf wenigstens meinen kleinen Finger schwören, dass ich exakt mit diesem Code mein Programm schon erfolgreich wochenlang benutzt habe.
Dass der Aufräum-Code nicht mehr funktioniert, fiel mir nach einer wochenlangen Arbeitspause (damit ist die Arbeit am Programm gemeint; gearbeitet habe ich weiter, allerdings für die Uni...) auf.
Ich bin alles andere als abergläubisch oder sonst in irgendeiner Richtung gläubig; gerade deshalb raubt mir dieses Problem den letzten Nerv. Für mich scheinbar aus dem NICHTS gibt mein Programm den Geist auf.
Ich stand da wie vom Blitz getroffen. Und stehe auch weiterhin so da.

Wäre das ein normaler Entwicklungsprozess-Fehler, würde ich einfach noch weitere 5 Tage suchen und ihn hoffentlich irgendwann finden.
Ich bin allerdings wirklich am Ende meines zugegeben kleinen Python-Lateins; der Fehler ist für Fortgeschrittene wahrscheinlich schnell zu finden.
Ich hoffe, ich habe nichts vergessen, höre jetzt auf zu schreiben und bedanke mich schon mal für wenigstens Anteilnahme an diesem Geisterhaus.

Gruß,
EdgarButan
Zuletzt geändert von EdgarButan am Freitag 17. September 2010, 18:59, insgesamt 1-mal geändert.
lunar

@EdgarButan: Du glaubst doch nicht tatsächlich, dass jemand so jetzt den Fehler in Deinem Programm finden kann? Ich kann allenfalls raten, dass "engine" zu dem Zeitpunkt, an dem "aboutToQuit" ausgelöst wird, nicht mehr existiert, weil weder Python noch Qt eine Referenz dieses Objekts halten, und es in Folge dessen vom GC abgeräumt wird. Warum und wo genau das passiert, kann man Dir bei derart belanglosen Quelltextausschnitten nicht sagen.
EdgarButan
User
Beiträge: 7
Registriert: Mittwoch 15. September 2010, 17:56

Nun ja, ich habe geschrieben, dass ich auf die Instanz weiterhin direkt zugreifen kann (nicht per Signal) UND die gewünschte Methode aus anderen Klassen heraus aufrufbar ist, auch zum Zeitpunkt "aboutToQuit".
Dieses Problem kann es demnach nicht sein.

Sehr viel komplizierter ist das Programm in diesem Bereich nicht.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

EdgarButan hat geschrieben:Sehr viel komplizierter ist das Programm in diesem Bereich nicht.
Dann kannst Du es ja auch in einer minimalen Version hier posten, oder? ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
EdgarButan
User
Beiträge: 7
Registriert: Mittwoch 15. September 2010, 17:56

zellen.py:

Code: Alles auswählen

from klassen import *

app = QApplication(sys.argv)

db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("datenbank1.db")
db.open()

engine = DatenEngine(db)
l = engine.returnData()

fenster = Hauptfenster()
scene = Scene(fenster, fenster.gview, l, engine)
app.aboutToQuit.connect(engine.hans)

fenster.show()

sys.exit(app.exec_())

klassen.py:

Code: Alles auswählen

import math
import random
import gc
from PyQt4.QtGui import * 
from PyQt4 import QtCore
from PyQt4.QtSql import QSqlDatabase, QSqlQuery

class DatenEngine(QtCore.QObject):
  def __init__(self, db):
    self.db = db
  
  def hans(self):
    print "hans"

class Scene(QGraphicsScene):
  def __init__(self, fenster, parent, l, engine):
    QGraphicsScene.__init__(self)
    self.fenster = fenster
    self.parent = parent
    self.l = l
    self.engine = engine

  def hans(self):      ##Wenn ich aboutToQuit hiermit verbinde, wird engine.hans() aufgerufen
     print self.engine
     self.engine.hans()

Ich glaube nicht, dass das sehr viel mehr ist, als ich vorher geschrieben habe, aber das müsst ihr beurteilen.
lunar

Ein bisschen mehr sollte es schon sein, denn in dieser Form ist das Programm nicht lauffähig. Zeige ein lauffähiges, minimales Beispiel, welches Dein Problem reproduzieren kann.
EdgarButan
User
Beiträge: 7
Registriert: Mittwoch 15. September 2010, 17:56

Ok.

zellentest.py:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
from klassen import *

app = QApplication(sys.argv)

db = QSqlDatabase.addDatabase("QSQLITE")
db.setDatabaseName("datenbank1.db")
db.open()

engine = DatenEngine(db)

fenster = QMainWindow()
scene = Scene(engine)
app.aboutToQuit.connect(engine.hans)                                         #Löst nicht aus
QtCore.QObject.connect(app, QtCore.SIGNAL("aboutToQuit()"), engine.hans)     #Löst nicht aus
app.aboutToQuit.connect(scene.hans)                                          #Löst aus

fenster.show()

sys.exit(app.exec_())
klassen.py:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: utf-8 -*-

import math
import random
import gc
from PyQt4.QtGui import * 
from PyQt4 import QtCore
from PyQt4.QtSql import QSqlDatabase, QSqlQuery

class DatenEngine(QtCore.QObject):
  def __init__(self, db):
    self.db = db
  
  def hans(self):
    print "hans"

class Scene(QGraphicsScene):
  def __init__(self, engine):
    QGraphicsScene.__init__(self)
    self.engine = engine

  def hans(self):      ##Wenn ich aboutToQuit hiermit verbinde, wird engine.hans() aufgerufen
    print "hans() aus Scene heraus ausgelöst:"
    self.engine.hans()
Jetzt ist das Beispiel so einfach, dass die Lösung eigentlich nur noch äußerst peinlich für mich werden kann.
lunar

Der Fehler ist, dass DatenEngine zwar von QObject ableitet, aber dessen Konstruktor nicht aufruft. Faktisch ist DatenEngine also halb-initialisierter Datenmüll, kein Wunder, dass es nicht richtig funktioniert.
EdgarButan
User
Beiträge: 7
Registriert: Mittwoch 15. September 2010, 17:56

Oh Gott.
Ich wüsste gerne, wie das dann jemals funktionieren konnte (und das hat es definitiv).
Aber das herauszufinden ist wohl nicht mehr möglich.

Vielen Dank.
lunar

@EdgarButan: Das Log des Versionskontrollsystems könnte Dir diese Frage beantworten ... ins Blaue geraten würde ich sagen, dass der Aufruf des Basiskonstruktors beim Refactoring verloren gegangen ist.
Antworten