PyQt4 Signal mit eigener Klasse als Parameter

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
DrFaust
User
Beiträge: 21
Registriert: Freitag 15. Oktober 2010, 23:10

Hallo allerseits,

ich schreibe eine Anwendung, in der ich den Qt-Signal-Slot-Mechanismus verwende um Informationen innerhalb des Programms auszutauschen. Die meisten meiner Klassen sind QObjects und per signal-slot verbunden. Nun habe ich eine Klasse A, deren Objekte je eine ganze Liste von Objekten der Klasse B hält. Nun sollen die einzelnen Objekte der Klasse B ihren 'Papa' darüber informieren, wenn bei ihnen bestimmte Ereignisse passieren. Damit das 'Papa'-Objekt weiß von welcher der B Instanzen die Benachrichtigung kommt, sollen die Klassen beim emit jeweils self, also einen Pointer auf sich selber übergeben. Das ganze sieht etwa so aus:

Code: Alles auswählen

class A(QtCore.QObject):
    def __init__(self):
        super(A,self).__init__()
    
    def get_signal(self,child):
        print(child)

class B(QtCore.QObject):

    mySignal = QtCore.pyqtSignal(B)    

    def __init__(self):
        super(B,self).__init__()
    
Problem: Ich kann das signal von B nicht wie da angegeben erstellen, da die Klasse B zu diesem Zeitpunkt noch gar nicht existiert. In C ergibt sich das Problem nicht, aber in Python eben schon (fühlt sich komisch an das zu sagen). Wie löse ich so was?

Danke schon mal

DrFaust
Zuletzt geändert von DrFaust am Samstag 16. Oktober 2010, 20:34, insgesamt 1-mal geändert.
BlackJack

Die Klasse wird erst an den Namen `B` gebunden, wenn die Klassendefinition komplett abgearbeitet wurde. Hast Du schon versucht *danach* das Attribut zu erstellen?

Code: Alles auswählen

class B(QtCore.QObject):
    def __init__(self):
        super(B, self).__init__()

B.mySignal = QtCore.pyqtSignal(B)
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Hi,
Versuch mal im Slot mit

Code: Alles auswählen

self.sender()
an die Referenz des sendenden Objektes zu kommen.

Gruß
Benutzeravatar
DrFaust
User
Beiträge: 21
Registriert: Freitag 15. Oktober 2010, 23:10

@BlackJack

Warum das nicht funktioniert ist klar. Ich habe deinen Lösungsansatz auch versucht. Ich kann aber mit denn auftauchenden Fehlermeldung nicht viel anfangen:

Code: Alles auswählen

Object::connect: Use the SIGNAL macro to bind B::(QObject*)
Traceback (most recent call last):
  File "/home/nils/ma_workspace/PyDDHelper/experiments/foren_experiment.py", line 32, in <module>
    b1.mySignal.connect(a.get_signal)
TypeError: connect() failed between [B] and unislot()
Der Code den ich benutzt habe ist der folgende:

Code: Alles auswählen

from PyQt4 import QtCore

class A(QtCore.QObject):
    def __init__(self):
        super(A,self).__init__()
   
    def get_signal(self,child):
        print(child)

class B(QtCore.QObject):  

    def __init__(self,name):
        super(B,self).__init__()
        self.name = name
    
    def __str__(self):
        return self.name
    
    def emit_signal(self):
        self.mySignal.emit(self)
    
B.mySignal = QtCore.pyqtSignal(B)
a = A()
b1 = B("b1")
b2 = B("b2")
b1.mySignal.connect(a.get_signal)
b2.mySignal.connect(a.get_signal)

b1.emit_signal()
b2.emit_signal()
Außerdem vermute ich mal, dass man da Erstellen des signals vor mehrfacher Ausführung schützen sollte. Ich bin mir nicht sicher, was passiert, wenn das entsprechende Modul an mehreren Stellen eingebunden wird. Wird der Code dann mehrfach ausgeführt?!?

Ein solcher Schutz könnte etwa so aussehen:

Code: Alles auswählen

if not "mySignal" in dir(B):
    B.mySignal = QtCore.pyqtSignal(B)
Zuletzt geändert von DrFaust am Samstag 16. Oktober 2010, 20:34, insgesamt 1-mal geändert.
Benutzeravatar
DrFaust
User
Beiträge: 21
Registriert: Freitag 15. Oktober 2010, 23:10

@ichisich

Der entsprechende Code wäre dann wohl dieser:

Code: Alles auswählen

from PyQt4 import QtCore

class A(QtCore.QObject):
    def __init__(self):
        super(A,self).__init__()
   
    def get_signal(self):
        print(self.sender())

class B(QtCore.QObject):  

    mySignal = QtCore.pyqtSignal()

    def __init__(self,name):
        super(B,self).__init__()
        self.name = name
    
    def __str__(self):
        return self.name
    
    def emit_signal(self):
        self.mySignal.emit()

a = A()
b1 = B("b1")
b2 = B("b2")
b1.mySignal.connect(a.get_signal)
b2.mySignal.connect(a.get_signal)

b1.emit_signal()
b2.emit_signal()
Scheint zu funktionieren. Löst zwar nicht das Problem wie man signals erstellt, die die Objekte der eigenen Klasse als Parameter haben. Löst aber mein Problem.

Danke

DrFaust
Zuletzt geändert von DrFaust am Samstag 16. Oktober 2010, 20:35, insgesamt 2-mal geändert.
lunar

Statt des impliziten Durchgriffs auf den Sender halte ich es für sinnvoller, eine partielle Funktion als Slot zu verwenden. Die Signatur bleibt also weiterhin "get_signal(self, child)", und die Verbindung der Slots sieht wie folgt aus:

Code: Alles auswählen

from functools import partial
b1.mySignal.connect(partial(a.get_signal, b1))
b2.mySignal.connect(partial(a.get_singal, b2))
Diese Vorgehensweise hat den Vorteil, auch bei solchen Slots zu funktionieren, die nicht Attribut einer von QObject abgeleiteten Klasse sind.
Benutzeravatar
DrFaust
User
Beiträge: 21
Registriert: Freitag 15. Oktober 2010, 23:10

@lunar

Ich kenne partials nicht, aber nach dem was ich gerade darüber gelesen habe bleibt aber ja das Problem, dass ich kein signal definieren kann, das ein Objekt der eigenen Klasse als Parameter hat bestehen. Deine Lösung würde verändert ja nur den connect Aufruf, nicht aber die Definition des Signals. Und da geht es ja schon schief. Oder habe ich dich falsch verstanden?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Und genau dieses Problem loest partial: http://docs.python.org/library/functool ... ls.partial
Und nein, nicht der `connect`-Aufruf wird veraendert, sondern das Funktionsobjekt, das an `connect` uebergeben wird.
lunar

@DrFaust: "functools.partial" erzeugt einfach eine neue Funktion, indem gewissen Argumente der ursprünglichen Funktion mit festen Werten vorbelegt werden. Probiere es doch einfach einmal im Interpreter aus. Du kannst mir mithin vertrauen, dass dieser Ansatz Dein Problem löst.
Benutzeravatar
DrFaust
User
Beiträge: 21
Registriert: Freitag 15. Oktober 2010, 23:10

Hmm, das Problem war eher, dass ich mir nicht sicher war, was ich jetzt wie ändern sollte (was wohl wiederum mit meinem mangelnden Verständnis von partials zu tun hat). Aber ich habe jetzt so lange rumprobiert, bis es ging. Und ich denke, ich blicke so langsam, was da passiert. partial(a.get_signal, b1) Definiert mir quasi einen neuen Slot, der den Aufruf an get_signal durchtunnelt, dabei aber den Parameter auf b1 setzt. Genau genommen habe ich also hinterher für n B-Objekte auch n slots. Korrekt?

Wie auch immer, das löst das Problem vollständig. Danke euch allen.

Ciao

DrFaust
lunar

@DrFaust: Ja, korrekt (einigermaßen ;) ). Versuche aber vielleicht, das Konzept noch allgemeiner zu begreifen. Diese Technik funktioniert nicht nur für Slots, sondern für jede Art von Funktion.
Antworten