Hallo,
ich versuche gerade den Signal-Slot-Mechanismus von PyQt zu verstehen. Dazu habe ich mir zuerst die Qt-Dokumentation angeschaut. Die meine ich halbwegs zu verstehen. Signals und Slots sind hier beides public-Funktionen, wobei die Signal-Funktion nur deklariert und nicht implementiert wird. Die Arbeit übernimmt das MOS. Korrekt?
In PyQt gibt es die Klasse QtCore.pyqtSignal. Zumindest glaube ich, dass es sich hierbei um eine Klasse handelt, finden kann ich sie nicht unter https://docs.huihoo.com/pyqt/PyQt5/QtCore.html.
Um pyqtSignal nutzen zu können lege ich eine Klassenvariable (class attribute) an. An dieser Stelle handelt es sich noch um ein unbound-signal. PyQt übernimmt dann die Bindung an eine Instanz (?). Warum wird an dieser Stelle die unbound-bound-Methode verwendet? Mir ist der Nutzen nicht klar.
Vielen Dank und Viele Grüße
Signal-Slot-Mechanismus von PyQt
- __blackjack__
- User
- Beiträge: 13931
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@lokiak: Signale sind keine Funktionen. Also zumindest nicht auf der Python-Seite, wo sie ein Datentyp/eine Klasse sind (oder mehrere). Bei C++ würde ich sagen ist das was ”eigenes”, denn da wird die Sprache um das Signal/Slot-Konzept mit den Schlüssworten ``signals`` und ``emit`` erweitert.
Was `pyqtSignal()` letztendlich ist, Klasse oder Funktion, ist ein Implementierungsdetail. Benutzt wird es wie eine Funktion, weshalb es wahrscheinlich nicht bei den Klassen von `QtCore` gelistet ist.
Wie sollte es denn ohne unbound/bound funktionieren? Man definiert/deklariert das Signal für die Klasse, dass heisst jedes Exemplar das erstellt wird, soll das Signal senden können. Und man muss die Signale von der Klasse abfragen können, weil Qt das so vorsieht. Die Exemplare müssen dann aber jeweils ein eigenes, an das Exemplar gebundene Signal haben, denn man verbindet ja die Signale von individuellen Objekten mit Slots, und wenn man ein Signal sendet, dann von einem konkreten Objekt und nicht für alle Objekte der jeweiligen Klasse.
Das ist weniger eine Frage des Nutzens, sondern das man das so machen muss wenn man das Signal/Slot-Konzept von Qt in Python abbilden will.
Was `pyqtSignal()` letztendlich ist, Klasse oder Funktion, ist ein Implementierungsdetail. Benutzt wird es wie eine Funktion, weshalb es wahrscheinlich nicht bei den Klassen von `QtCore` gelistet ist.
Wie sollte es denn ohne unbound/bound funktionieren? Man definiert/deklariert das Signal für die Klasse, dass heisst jedes Exemplar das erstellt wird, soll das Signal senden können. Und man muss die Signale von der Klasse abfragen können, weil Qt das so vorsieht. Die Exemplare müssen dann aber jeweils ein eigenes, an das Exemplar gebundene Signal haben, denn man verbindet ja die Signale von individuellen Objekten mit Slots, und wenn man ein Signal sendet, dann von einem konkreten Objekt und nicht für alle Objekte der jeweiligen Klasse.
Das ist weniger eine Frage des Nutzens, sondern das man das so machen muss wenn man das Signal/Slot-Konzept von Qt in Python abbilden will.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
— Scott Bellware
Du meinst den MOC, nicht MOS. Der erzeugt die notwendigen Meta-Objekt-Implementierungen und die Methodenrümpfe für die Signale. Slots können auch private sein, ist aber letztlich wurscht, weil sei via dem Meta-Objekt immer aufgerufen werden können.
Und genau dieses Meta-Objekt ist auch der Grund, warum das erstmal Klassenattribute sind. Denn damit die Python-Klassen und daraus erzeugte Objekte mit C++ interagieren können, braucht es C++-Meta-Objekte, und ihre QObject-abgeleitete C++-Klasse, “auf Vorrat”. Und die modifiziert PyQt dann und legt die gewünschten Signale und Slots an. Es gibt davon auch nur eine begrenzte Anzahl, aber normalerweise reicht das aus. Und das sind ja pro Klasse(!) ein Objekt. Nicht pro Instanz. Daher sind das auch Klassen-Attribute. Zur Laufzeit wird dann beim erzeugen eines Objektes das dazugehörige C++-QObject erzeugt, damit die ganze Maschinerie funktioniert.
Und genau dieses Meta-Objekt ist auch der Grund, warum das erstmal Klassenattribute sind. Denn damit die Python-Klassen und daraus erzeugte Objekte mit C++ interagieren können, braucht es C++-Meta-Objekte, und ihre QObject-abgeleitete C++-Klasse, “auf Vorrat”. Und die modifiziert PyQt dann und legt die gewünschten Signale und Slots an. Es gibt davon auch nur eine begrenzte Anzahl, aber normalerweise reicht das aus. Und das sind ja pro Klasse(!) ein Objekt. Nicht pro Instanz. Daher sind das auch Klassen-Attribute. Zur Laufzeit wird dann beim erzeugen eines Objektes das dazugehörige C++-QObject erzeugt, damit die ganze Maschinerie funktioniert.
- __blackjack__
- User
- Beiträge: 13931
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@__deets__: Ich vermute mit „MOS“ war „Meta-Object System“ gemeint. Unter der Überschrift gibt es einen kurzen Überblick in der Qt-Dokumentation.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
— Scott Bellware
@blackjack:
Aber, wie gesagt, meine Programmierkenntnisse sind begrenzt. Arbeite aber daran 
VG
Signale sind keine Funktionen. Also zumindest nicht auf der Python-Seite, wo sie ein Datentyp/eine Klasse sind (oder mehrere)
Jetzt komme ich durcheinander. 'pyqtSignal' kann beides sein? Also mit meinen beschränkten Programmierkenntnissen hätte ich gedacht, dass man entweder-oder hat. Klasse oder Funktion. Und in diesem Fall hätte ich gedacht, dass 'pyqtSignal' eine Klasse ist, mit den Klassenfunktionen 'emit()' und 'connect'. Anders kann ich mir das im folgenden Beispielcode nicht erklären.Was `pyqtSignal()` letztendlich ist, Klasse oder Funktion, ist ein Implementierungsdetail. Benutzt wird es wie eine Funktion, weshalb es wahrscheinlich nicht bei den Klassen von `QtCore` gelistet ist.
Code: Alles auswählen
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import pyqtSignal, Qobject
class Communicate(QObject):
closeApp = pyqtSignal()
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.c = Communicate()
self.c.closeApp.connect(self.close)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Mouse events')
self.show()
def mousePressEvent(self, event):
self.c.closeApp.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

Also so wie Du das hier erklärst klingt das einfach nach einer Klassenvariable, also einfach nach einer Variablen, die, wenn eine Instanz dieser Klasse erzeugt wird, eben dieser einen Instanz gehört.Wie sollte es denn ohne unbound/bound funktionieren? Man definiert/deklariert das Signal für die Klasse, dass heisst jedes Exemplar das erstellt wird, soll das Signal senden können. Und man muss die Signale von der Klasse abfragen können, weil Qt das so vorsieht. Die Exemplare müssen dann aber jeweils ein eigenes, an das Exemplar gebundene Signal haben, denn man verbindet ja die Signale von individuellen Objekten mit Slots, und wenn man ein Signal sendet, dann von einem konkreten Objekt und nicht für alle Objekte der jeweiligen Klasse.
VG
- __blackjack__
- User
- Beiträge: 13931
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@lokiak: `pyqtSignal()` kann nicht *gleichzeitig* eine Funktion und eine Klasse sein, aber aus der Dokumentation geht nicht hervor, dass es eine Klasse ist. Dort wird es wie eine Funktion beschrieben, und es könnte auch problemlos eine sein.
Tatsächlich ist es wohl eine Klasse, aber die hat weder eine `connect()`- noch eine `emit()`-Methode. Denn auf diese Objekte greift man auf Exemplaren ja gar nicht zu. Dort hat jedes Exemplar noch mal ein eigenes Objekt von einem anderen Typ: `pyqtBoundSignal`. Die werden beim Initialisieren erstellt.
Signale als Klassenattribute würden nicht funktionieren. `QPushButton` haben ein `clicked`-Signal. Das darf es nicht nur einmal geben, denn wenn man zwei Buttons erzeugt, will man ja in der Regel, dass jeder davon sein eigenes `clicked`-Signal hat, das man unabhängig an unterschiedliche Slots binden kann. Das geht nicht wenn es auf der Klasse nur *ein* Signal-Objekt gäbe. Auf der Klasse muss aber definiert werden, welche Signale diese Klasse hat. Weil das Meta-Object-Modell von Qt diese Informationen über die Klasse verfügbar macht.
Tatsächlich ist es wohl eine Klasse, aber die hat weder eine `connect()`- noch eine `emit()`-Methode. Denn auf diese Objekte greift man auf Exemplaren ja gar nicht zu. Dort hat jedes Exemplar noch mal ein eigenes Objekt von einem anderen Typ: `pyqtBoundSignal`. Die werden beim Initialisieren erstellt.
Signale als Klassenattribute würden nicht funktionieren. `QPushButton` haben ein `clicked`-Signal. Das darf es nicht nur einmal geben, denn wenn man zwei Buttons erzeugt, will man ja in der Regel, dass jeder davon sein eigenes `clicked`-Signal hat, das man unabhängig an unterschiedliche Slots binden kann. Das geht nicht wenn es auf der Klasse nur *ein* Signal-Objekt gäbe. Auf der Klasse muss aber definiert werden, welche Signale diese Klasse hat. Weil das Meta-Object-Modell von Qt diese Informationen über die Klasse verfügbar macht.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
— Scott Bellware
@blackjack:
Mit Exemplare meinst Du Instanzen? Sorry, ich komme nicht mit.
Noch einmal zurück zu Deiner 1sten Antwort:
Sind es nicht also schlussendlich einfache Funktionen in C++?
Vielen Dank auch an deets, Du hast es mir ebenfalls versucht zu erklären!
VG
Der Grund für mich anzunehmen, dass es sich bei 'pyqtSignal' um eine Klasse mit den 'connect()' und 'emit()' Funktionen handelt, war die Punktnotation im gezeigt Beispielcode. So kenn ich es mit meinen limitierten Programmierkenntnissen: man schreibt eine Klasse mit Funktionen, erzeugt eine Instanz und greift über 'Namen_der_Instanz.Funktionsnamen" auf die Funktion zu. Aber das scheint hier anders zu sein.Tatsächlich ist es wohl eine Klasse, aber die hat weder eine `connect()`- noch eine `emit()`-Methode. Denn auf diese Objekte greift man auf Exemplaren ja gar nicht zu. Dort hat jedes Exemplar noch mal ein eigenes Objekt von einem anderen Typ: `pyqtBoundSignal`. Die werden beim Initialisieren erstellt.
Mit Exemplare meinst Du Instanzen? Sorry, ich komme nicht mit.
Auf https://www.riverbankcomputing.com/stat ... slots.html habe ich gelesen >> A signal (specifically an unbound signal) is a class attribute. << Deshalb sprach ich von Klassenattributen.Signale als Klassenattribute würden nicht funktionieren.
Noch einmal zurück zu Deiner 1sten Antwort:
Auf https://doc.qt.io/qt-5/signalsandslots.html fand ichBei C++ würde ich sagen ist das was ”eigenes”, denn da wird die Sprache um das Signal/Slot-Konzept mit den Schlüssworten ``signals`` und ``emit`` erweitert.
Signals are public access functions and can be emitted from anywhere, but we recommend to only emit them from the class that defines the signal and its subclasses.
Sind es nicht also schlussendlich einfache Funktionen in C++?
Meine naive Idee (wie es funktionieren könnte) bisher: es gibt eine Klasse 'pyqtSignal' mit einer Funktion, z.B. emit(), die bei der Instanzbildung über 'self' eine Referenz auf sich selbst erhält. Die Instanz hat ja eine eigene Id (Speicheradresse). Mit Bezug auf die Id könnte die Rückgabe der emit-Funktion so etwas sein wie 'Id_der_Instanz_Signal'. Dann wäre das Signal eindeutig mit der Instanz verbunden. So in etwa, dachte ich bislang, würde das ablaufen. Aber es scheint komplexer zu sein, und ich verstehe offensichtlich noch nicht ansatzweise, wie Python über PyQt und Qt zusammen mit dem MOC funktionieren. Da werde ich mich wohl noch belesen müssen (für gute Quellen bin ich dankbar).Wie sollte es denn ohne unbound/bound funktionieren? Man definiert/deklariert das Signal für die Klasse, dass heisst jedes Exemplar das erstellt wird, soll das Signal senden können. Und man muss die Signale von der Klasse abfragen können, weil Qt das so vorsieht. Die Exemplare müssen dann aber jeweils ein eigenes, an das Exemplar gebundene Signal haben, denn man verbindet ja die Signale von individuellen Objekten mit Slots, und wenn man ein Signal sendet, dann von einem konkreten Objekt und nicht für alle Objekte der jeweiligen Klasse.
Vielen Dank auch an deets, Du hast es mir ebenfalls versucht zu erklären!
VG
Ich vermute mal eher, dass hier das descriptor Protokoll zum Einsatz kommt, um aus der Klassenvariable ein an die konkrete Instanz gebundenes Signal Objekt zu machen.
Aber warum brauchst du diesen Detailgrad? Die konkrete Implementierung kann man im Code sehen, aber die ist doch für Nutzung egal.
Aber warum brauchst du diesen Detailgrad? Die konkrete Implementierung kann man im Code sehen, aber die ist doch für Nutzung egal.
- __blackjack__
- User
- Beiträge: 13931
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@lokiak: Also erstens muss `pyqtSignal()` keine Klasse sein, nur weil man auf dem Ergebnis mittels Punktoperator auf Attribute zugreifen kann. Das geht ja auch wenn das eine Funktion (oder Methode) ist, die ein entsprechendes Objekt als Ergebnis liefert.
Nun ist `pyqtSignal` eine Klasse, aber die muss keine `emit()`- oder `connect()`-Methode haben — hat sie auch nicht — weil das `pyqtSignal`-Objekt ein *Klassenattribut* ist, `emit()` und `connect()` aber nicht auf diesem Klassenattribut, sondern auf einem gleichnamigen *Instanzattribut* aufgerufen werden, das einen anderen Typ als `pyqtSignal` hat. Nämlich `pyqtBoundSignal`:
Deine Idee funktioniert so nicht weil das Exemplar von `pyqtSignal` an die *Klasse* gebunden wird. Woher sollte eine `emit()`-Methode *dort* wissen, für welches konkrete Objekt von dem Typ der Klasse sie aufgerufen wurde? Genau dafür gibt es dann ein `pyqtBoundSignal`-Objekt auf jedem einzlenen Exemplar, das aus der Klasse mit dem `pyqtSignal` erstellt wird.
Nun ist `pyqtSignal` eine Klasse, aber die muss keine `emit()`- oder `connect()`-Methode haben — hat sie auch nicht — weil das `pyqtSignal`-Objekt ein *Klassenattribut* ist, `emit()` und `connect()` aber nicht auf diesem Klassenattribut, sondern auf einem gleichnamigen *Instanzattribut* aufgerufen werden, das einen anderen Typ als `pyqtSignal` hat. Nämlich `pyqtBoundSignal`:
Code: Alles auswählen
In [156]: class A(PyQt5.QtCore.QObject):
...: a_signal = PyQt5.QtCore.pyqtSignal()
...:
In [157]: A.a_signal
Out[157]: <unbound PYQT_SIGNAL a_signal()>
In [158]: A.a_signal.__class__
Out[158]: PyQt5.QtCore.pyqtSignal
In [159]: A.a_signal.emit
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-159-ea3197a3e275> in <module>
----> 1 A.a_signal.emit
AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'emit'
In [160]: a = A()
In [161]: a.a_signal
Out[161]: <bound PYQT_SIGNAL a_signal of A object at 0x7f9902e9bee8>
In [162]: a.a_signal.__class__
Out[162]: PyQt5.QtCore.pyqtBoundSignal
In [163]: a.a_signal.emit()
In [164]:
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
— Scott Bellware
Und genau diese „Wandlung“ von unbound zu bound erledigt das descriptor-Protokoll, siehe https://github.com/baoboa/pyqt5/blob/11 ... l.cpp#L274
- __blackjack__
- User
- Beiträge: 13931
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Wobei das, als ergänzende Anmerkung, auch wieder nur ein Implementierungsdetail ist. Das hätte man auch in `__init__()` (oder `__new__()`) auf `QObject` regeln können, das dort ”normale” Attribute für Signale auf der Klasse erstellt werden.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
— Scott Bellware
@blackjack:
VG
Kannst Du mir vielleicht ein ganz simples Beispiel nennen? Danke!Also erstens muss `pyqtSignal()` keine Klasse sein, nur weil man auf dem Ergebnis mittels Punktoperator auf Attribute zugreifen kann. Das geht ja auch wenn das eine Funktion (oder Methode) ist, die ein entsprechendes Objekt als Ergebnis liefert.
Gebunden wird doch eine Instanz (=Exemplar?) von `pyqtSignal` (oder `pyqtBoundSignal`?) an die Instanz der von QObject abgeleiteten Klasse. Korrekt?Deine Idee funktioniert so nicht weil das Exemplar von `pyqtSignal` an die *Klasse* gebunden wird.
Dynamisch, in der Art der factory design pattern hätte ich gedacht.Woher sollte eine `emit()`-Methode *dort* wissen, für welches konkrete Objekt von dem Typ der Klasse sie aufgerufen wurde? Genau dafür gibt es dann ein `pyqtBoundSignal`-Objekt auf jedem einzlenen Exemplar, das aus der Klasse mit dem `pyqtSignal` erstellt wird.
Danke, ich werde es mir ansehen.Und genau diese „Wandlung“ von unbound zu bound erledigt das descriptor-Protokoll,...
VG
Code: Alles auswählen
def pyqtSignal():
return SomeClass()
Die Instanz eine Klasse ist ein Objekt. Und daran wird pyQtSignal NICHT gebunden. Wie BJ zeigt. Sondern an die Klasse selbst,und durch __get__ *bei Bedarf* ein bound Signal beim Zugriff via einer Instanz.
- __blackjack__
- User
- Beiträge: 13931
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@lokiak: Ein Beispiel für eine Funktion hat __deets__ ja bereits gezeigt. Klasse und Funktion sind in Python an dieser Stelle austauschbar, weil man eine Klasse auch als (Fabrik)Funktion sehen kann, die beim Aufruf ein Objekt vom entsprechenden Typ erzeugt. Sowohl Klasse als auch Funktion sind „callable“, und das ist hier für den Benutzer die einzige API die er braucht — etwas das man aufrufen kann und das ein passendes Objekt liefert.
Gebunden wird ein Objekt vom Typ `pyqtSignal` an die von QObject abgeleitete Klasse. Nicht an eine Instanz von dieser Klasse sondern an die Klasse selbst. Das ist in Python auch ein Objekt. Wie alles was man an einen Namen binden kann, in Python ein Objekt ist. Auch Module, Funktionen, und Methoden beispielsweise.
Das `pyqtBoundSignal` entsteht beim Zugriff auf das Attribut durch den Aufruf der `__get__()`-Methode vom `pyqtSignal`-Objekt durch das Descriptor-Protokoll. Mit dem hat man normalerweise sehr selten direkt selbst etwas zu tun, das ist aber wichtiger Bestandteil der ”Magie” für Klassen. Methoden funktionieren beispielsweise auch darüber das Funktionen eine `__get__()`-Methode besitzen, damit aus der Funktion auf dem Klassen-Objekt eine Methode wird, wenn man über ein Objekt vom Typ dieser Klasse zugreift.
Gebunden wird ein Objekt vom Typ `pyqtSignal` an die von QObject abgeleitete Klasse. Nicht an eine Instanz von dieser Klasse sondern an die Klasse selbst. Das ist in Python auch ein Objekt. Wie alles was man an einen Namen binden kann, in Python ein Objekt ist. Auch Module, Funktionen, und Methoden beispielsweise.
Das `pyqtBoundSignal` entsteht beim Zugriff auf das Attribut durch den Aufruf der `__get__()`-Methode vom `pyqtSignal`-Objekt durch das Descriptor-Protokoll. Mit dem hat man normalerweise sehr selten direkt selbst etwas zu tun, das ist aber wichtiger Bestandteil der ”Magie” für Klassen. Methoden funktionieren beispielsweise auch darüber das Funktionen eine `__get__()`-Methode besitzen, damit aus der Funktion auf dem Klassen-Objekt eine Methode wird, wenn man über ein Objekt vom Typ dieser Klasse zugreift.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
— Scott Bellware