Werteübergabe SIGNAL/SLOT

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
BlackJack

@Cascoin: Mit Name meinte ich Name. Und ich verstehe nicht so ganz was Du darüber im zweiten Absatz schreibst. Natürlich kann man Namen auf Modulebene auch Methoden als Werte zuweisen. Du versuchst in der alten `Connection_SAS()` aber auf `Sig` auf Modulebene zuzugreifen, dass es dort einfach nicht gibt. Genausowenig wie `Ausgabe`. Beides sind aber sehr wohl Attribute auf Deiner Klasse und damit auch auf Exemplaren davon. Das muss man beim Zugriff aber auch sagen, denn sonst wird halt im Modulnamensraum gesucht, wo es die Namen nicht gibt. Du hast eben nicht wie im Beispiel aus der Dokumentation über `self.Sig` auf das Signal-Objekt zugegriffen, sondern nur `Sig` geschrieben. Das führt zu „global name Sig is not defined”.

Eine Methode wird automatisch an ein Exemplar gebunden — da muss man nicht extra etwas für tun. Und wenn man sie auf einem Exemplar aufruft, wird dieses Exemplar auch automatisch als erstes Argument übergeben. Also braucht man in der Argumentliste auch einen formalen Parameter an den es dann gebunden werden kann. Per Konvention heisst der `self`.

Um mal ein bisschen pedantisch zu sein: Der Sendefunktion muss nicht der Datentyp `Student` übergeben werden, sondern ein Objekt vom Typ `Student`. `Student`, also der Datentyp ist ja selbst auch ein Objekt, aber das will man dort nicht als Wert übergeben, sondern einen konkreten Studenten.

Die Funktionssignatur kann ``def Sende(self, Student)``, ``def Sende(self, Sig)``, oder auch ``def Sende(self, Telefonbuch)`` sein. Das hat keine semantischen Auswirkungen. Man sollte einen Namen wählen, der die Bedeutung des Arguments gut beschreibt. Und man sollte sich hier mindestens an die Namenskonvention von Qt halten, denn auch dort sind Namen die mit Grossbuchstaben beginnen in der Regel für Datentypen reserviert. ``def sende(self, student)`` wäre also eine gute Wahl.

Ad 2.2) Das Signal sendet nicht den Datentyp `Signal` sondern den Datentyp `Student`.

Ad 2.3) Das wird in einem Fehler enden weil `Foo`-Objekte keine `emit()`-Methode haben. Das ist eine Methode auf Signalen. Und dann stimmt das Argument von der Methode nicht. Der Name `Sig` ist nicht auf Modulebene definiert, und Du willst ja auch gar nicht eine Liste mit dem Signal-Objekt als einzigem Element verschicken, sondern doch wohl das Objekt welches der Methode übergeben wurde.

Bei `Ausgabe` gibt es dann auch wieder ein Problem, nämlich das die Methode neben dem Exemplar auf dem sie aufgerufen wird, noch den Wert übergeben bekommt, welches das Signal emmitiert, mit dem die Methode verbunden ist. Dafür gibt es aber keinen formalen Parameter in der ``def``-Zeile. Wo glaubst Du denn das der Name `Student` innerhalb der Methode her kommt? Wo wird der an einen Wert gebunden, und an welchen?
Cascoin
User
Beiträge: 24
Registriert: Dienstag 17. Mai 2011, 18:12

Hallo, sorry war ne Woche weg und konnt deswegen nicht schneller antworten....

Ich fange mal von unten an auf deinen Beitrag zu antworten.

1:
Also zur Ausgabe:
Ich dachte das diese Methode automatisch weiß welches Signal sie bekommt. Wenn die Methode "Ausgabe" das wüßte, dachte ich, dass man ihr die Argumente nicht noch extra übergeben muss. Aber ist schon klarer wenn man dieser Methode student als Argument übergibt...

So siehts jetzt aus:

Code: Alles auswählen

    
def Ausgabe(self, student):
        print Student.name
Jetzt denke ich müßte der Ausgabe-Methode klar sein was zu printen ist.....

2: Ok, das habe ich auch nicht gewußt weil in der Doku nie ein Objekt eines Types emitiert wird....

Code: Alles auswählen

def Sende(self, student):
        self.emit(QtCore.SIGNAL('self.Sig'))
Was mich hier nur verwundert ist das der Sende-Methode ja eigenltich ein Objekt vom Typ Student übergeben muss wie du geschrieben hast.
Eigenltich will ich ja aber der Sende-Methode nur ein Signal übergeben das ja dann das Objekt vom Typ Student beinhaltet....

Zu deinem Vorschlag (ad 2.2) habe ich dann folgendermaßen geändert....

Code: Alles auswählen

Sig = pyqtSignal(Student)       # Definiert die Art meines Signals
Zu deinem ersten Absatz:
Also ich glaube ich mir ist immer noch nicht 100% klar was das soll. Wenn ich self.Sig() schreibe wird nach der Methode in dem Exemplar gesucht, ohne self wird auf Modulebene.... Hast ja auch so geschrieben....
Find ich komisch weil wenn ich eine Methode innerhalb einer Klasse definiere, möchte ich ja eigenltich immer, dass die Methode zu allen Exemplaren dieser Klasse gehört.

Naja, jetzt kommt momentan im Code keine Fehlermeldung mehr, aber funktionieren tuts auch nicht weil nichts ausgegeben wird....
Ich stell mal den kompletten Code von der Foo-Klasse rein

Code: Alles auswählen

class Foo(QObject):
    def __init__(self, parent=None):# Konstruktor
        QObject.__init__(self, parent)
        self.Connection_SAS()       # Verbindet Signal&Slot
        
    
    Sig = pyqtSignal(Student)       # Definiert die Art meines Signals

    
    def Connection_SAS(self):       #Verbindung von meinem Signal zur Ausgabe/Slot
        self.Sig.connect(self.Ausgabe)

    
    def Sende(self, student):       #Sendet Signal
        self.emit(QtCore.SIGNAL('Sig'))
       
   
    def Ausgabe(self, student):     #Ausgabe
        print student.name
Woran liegt dasw jetzt? Ich denke der Grund ist, das nichts emitiert wird... In der Doku gibts kein Bsp wo ein Objekt emitiert wird... In verschiedenen Beiträgen hier im Forum hab ich aber den Befehl so "self.emit(QtCore.SIGNAL('Sig'))" und so "self.emit(QtCore.SIGNAL("Sig"))" gesehen...
Hab das Gefühl das wenn ich den Befehl in "Sig" oder 'Sig' schreibe wird anstelle des Objekts ein String übergeben. Dann ist klar das nichts passiert.... Ohne "" und '' kommt aber wieder eine Fehlermeldung das er Sig nicht findet.....

Viele Grüße Cascoin
Zuletzt geändert von Cascoin am Dienstag 28. Juni 2011, 13:36, insgesamt 1-mal geändert.
BlackJack

@Cascoin: Ad 1) Methoden wissen nichts automatisch, mal von der Magie mit dem ersten Argument abgesehen was man bei gebundenen Methoden beim Aufruf nicht explizit übergeben muss. Signale und Slots aus Qt kennt die Sprache auch nicht, da verhält sich die Sprache also nicht plötzlich anders als bei ganz normalen Objekten und Methoden-Aufrufen. Wenn man einen Namen innerhalb einer Funktion oder Methode verwendet, dann muss der entweder dort gebunden worden sein, als Argument herein kommen, oder auf Modulebene vorhanden sein. (Einige wenige Namen stehen auch wirklich global über das `__builtin__`-Modul zur Verfügung.)

Ad 2) Beim Punktoperator auf ein Exemplar wird das Attribut erst auf dem Exemplar gesucht, und wenn es dort nicht gefunden wird, dann wird in den Klassen-Objekten gesucht, die zur Vererbungshierarchie des Objekts gehören. Alles was in einer Klasse definiert ist, kann man also auch auf Exemplaren finden.

In der Doku stehen `emit()`-Aufrufe und auch welche mit Objekten, also natürlich auch Objekte eines Typs, denn *jedes* Objekt hat einen Typ. Es gibt kein Objekt bei dem die `type()`-Funktion keinen Rückgabewert liefert.

Ich weiss nicht ob das da überhaupt so funktioniert, weil das ja eigentlich der alte Stil ist, den Du jetzt mit dem neuen mischst. Ich würde nicht `QObject.emit()` verwenden, sondern die `emit()`-Methode auf dem Signal selbst. Ist auch wieder weniger Tipparbeit. Und dann fehlt auch wieder das verschicken des `student`-Exemplars. Du scheinst da schon wieder darauf zu hoffen, dass Python Gedanken lesen kann und schon automatisch das macht, was Du gerne hättest, aber nicht explizit sagst. So funktioniert programmieren nicht. Du möchtest das Signal `Sig` mit dem Studenten als Datum emmitieren. Das muss man auch *genau so* schreiben und nicht hoffen das irgend ein Teil davon schon richtig von Python erraten wird.

Code: Alles auswählen

    def Sende(self, student):
        self.Sig.emit(student)
Cascoin
User
Beiträge: 24
Registriert: Dienstag 17. Mai 2011, 18:12

Hallo,
also wenn ich das was du mir geschrieben hast verwende funktioniert es so wie ich es mir vorgestellt habe!!!! Hammergeil.....
Ich poste grad mal den kompletten Code....

Das Importzeugs:

Code: Alles auswählen

from PyQt4.QtCore import *
from PyQt4 import QtGui, QtCore
import sys

Hier meine Beispielklasse:

Code: Alles auswählen

class  Student(QObject):           # Testklasse
    def __init__(self, name, Studium, Nr):
        QObject.__init__(self)
       
        self.Name = name
        self.Fach = Studium
        self.Matrikel = Nr
Meine Foo Klasse:

Code: Alles auswählen

class Foo(QObject):
    def __init__(self, parent=None):# Konstruktor
        QObject.__init__(self, parent)
        self.Connection_SAS()       # Verbindet Signal&Slot
        
    
    Sig = pyqtSignal(Student)       # Definiert die Art meines Signals

    
    def Connection_SAS(self):       #Verbindung von meinem Signal zur Ausgabe/Slot
        self.Sig.connect(self.Ausgabe)

    
    def Sende(self, student):       #Sendet Signal
        self.Sig.emit(student)
       
   
    def Ausgabe(self, student):     #Ausgabe
        print student.Name + " " + student.Fach
und zu guter Letzt noch die main()-Funktion:

Code: Alles auswählen

def main():                         # Main
    Andi = Student("Andi", "MB", 23523)

    Test = Foo()
    Test.Sende(Andi)
Also ich glaube das verschicken habe ich nicht vergessen, die Sende-Methode rufe ich in meiner Main() auf.... Kann man ja auch so machen.... Wie gesagt, es klappt..... Herzlichsten Dank!

Da ich das besagte Programm sauber korrigieren möchte, sodass es noch die nächsten Updates übersteht wollte ich fragen an welchen Stellen ich noch den alten mit dem neuen Style gemixt habe? Das mag ich noch ausmerzen....

mfg Cascoin
Cascoin
User
Beiträge: 24
Registriert: Dienstag 17. Mai 2011, 18:12

hallo,
noch eine kleine Frage hätte ich.

Und zwar wenn ich über diese Definition

Code: Alles auswählen

def Ausgabe(self, student):
noch

Code: Alles auswählen

@pyqtSlot(Student)
geschrieben,

also im Prinzip sieht das dann komplett so aus:

Code: Alles auswählen

@pyqtSlot(Student)
    def Ausgabe(self, student):     #Ausgabe
    ...................
    ...................
Meine Frage ist nun was das bringt, und ob das unbedingt notwendig ist?

In der Doku steht ja das es manchmal benötigt wird um eine Python-Methode explizit als Qt-Slot zu markieren um C++-Signaturen zu unterstützen.
Brauche ich das also nur wenn ich C++-Code in meinem Program habe? So verstehe ich das jedenfalls.
Unter einer C++-Signatur verstehe ich die formale Schnittstelle einer Funktion in C++ (steht so in Wiki)

Falls ich keinen C++ Code habe, ist das dann komplett sinnlos?

mfg Cascoin
lunar

"pyqtSlot()" wird vor allem deswegen benötigt, um Slots automatisch über ihren Namen zu verbinden. Wiewohl es nicht schadet, "pyqtSlot()" zu benutzen, es ist im Regelfall überflüssig.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Ach wenn mir erst ein paar Seiten PyQT4 bzw. PySide gegönnt hatte, hier mein Senf:

Code: Alles auswählen

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

import functools
import sys
from PyQt4 import QtGui

def print_the_message(message):
    print('Button says: ' + message)

def main():
    buttons = ('Hello World', 'Thanks for all the fish', '42',
               'Lets go to the stoning!')

    app = QtGui.QApplication(sys.argv)
    
    layout = QtGui.QVBoxLayout()
    
    widget = QtGui.QWidget()
    widget.setLayout(layout)
    widget.setWindowTitle('Buttons and partial')
    
    for name in buttons:
        button = QtGui.QPushButton(name, widget)
        button.clicked.connect(functools.partial(print_the_message, name))
        layout.addWidget(button)
    
    widget.show()
    
    app.exec_()
    
if __name__ == '__main__':
    main()
M.E. synthetisiert dieses Beispiel die Anmerkungen der letzten 3 Seiten dieses Threads.

Grüße ... Heiko
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
Cascoin
User
Beiträge: 24
Registriert: Dienstag 17. Mai 2011, 18:12

Hoi,
jetzt müßte ich doch nochmal was fragen....
Und zwar zu dem letzten Beispiel das ich reingestellt habe.... Soweit funktioniert das ja super....
Was ich jetzt grad allerdings probiere funktionert wieder nicht. Und zwar möchte ich die Sende-Methode auslagern. Und zwar in ein anderes Modul/Datei.
Wenn ich das mache, muß ich ja diese Methode in eine andere Klasse schreiben... Das Problem ist jetzt allerdings, dass diese Klasse(die die Sende Methode enthält) ja dann das Signal nicht kennt.... Das ist ja auch klar.... Jetzt frag ich mich wie ich dieser Klasse die Information übergebe, dass das Signal in einer anderen Klasse steht und schon definiert wurde.... Ich möchte das so einfach wie möglich gestalten... Soll ich der neuen Klasse(enthält die Sende-Methode) nun die Klasse übergeben in der das Signal definiert ist oder gibts da elegantere Möglichkeiten?

Angenommen ich übergebe der Sende-Klasse diejenige Klasse(hier: Foo) die das Signal enthält, dann hab ich da kurz eine Frage:
Prinzipiell funktioniert das bei mir und sieht so aus:

Code: Alles auswählen

class Klasse_Sende(QObject):
    def __init__(self, Foo, student):  # Konstruktor dem eine Klasse Foo und Sende übergeben wird.
        QObject.__init__(self)
        Foo.Sig.emit(student)            # In der Klasse Foo ist das Signal definiert, die Klasse student soll emittiert werden
Jetzt hätte ich es lieber so, dass es folgendermaßen ausschaut:

Code: Alles auswählen

class Klasse_Sende(QObject):
    def __init__(self, Foo, student):  # Konstruktor dem eine Klasse Foo und Sende übergeben wird.
        QObject.__init__(self)
        self.sende(student)              
        
    def sende(self, student)
         Foo.Sig.emit(student)            # In der Klasse Foo ist das Signal definiert, die Klasse student soll emittiert werden
-> wenn ich das so allerdings schreibe kennt er Foo nicht..... In dem obrigen Code funktionierts allerdings.....

Also meine Fragen:
1. Kann man das ganze Problem auch simpler gestalten oder muss ich immer Klassen an andere Klassen übergeben.
2. Was muss ich an meinem 2ten Codeschnippsel ändern damit das so funktioniert?


Hoffentlich hab ich mich nicht zu kompliziert ausgedrückt....

(Falls ihr euch fragt warum ich das jetzt komplizierter wie nötig mache liegt das daran das ich immer noch versuche etwas analoges zu einem Programm herzustellen das ich eigentlich korrigieren soll)

beste Grüße
Cascoin
BlackJack

@Cascoin: Ist `Foo` wirklich eine Klasse? Dann funkioniert das so in keinem der beiden Fälle richtig, weil `Sig` kein gebundenes Signal ist. Wenn man ein Exemplar aus einer Klasse erstellt, welche auf Klassenebene Signalobjekte enthält, dann wird aus diesem Signal auf dem Exemplar ein an dieses Exemplar gebundenes Signalobjekt erstellt. Jedes Exemplar emittiert ja seine eigenen Signale.

Das man die `emit()`-Methode eines Signals ausserhalb von Methoden der Klasse auf der das Signal definiert ist, aufruft, ist zumindest ungewöhnlich. Signale werden üblicherweise ja dazu benutzt um interne Zustandsänderungen eines Objekts nach aussen zu melden.
Cascoin
User
Beiträge: 24
Registriert: Dienstag 17. Mai 2011, 18:12

Hallo,
@BlackJack: Ok, das habe ich verstanden. Dann möchte ich ein gebundenes Signal erstellen....
Dazu "rudere" ich am besten einen Schritt zurück und poste nochmal meinen älteren Code, also den Code in dem die ganze Prozedur in einer Datei steht:

Also das senden eines Exemplars (einer Klasse) von einem anderen Exemplar einer anderen Klasse aus:

Also hier der Code:

1. Die Klasse die gesendet werden soll:

Code: Alles auswählen

# Meine Klasse die ich senden möchte
class  Student(QObject):           # Testklasse
    def __init__(self, name, Studium, Nr):
        QObject.__init__(self)
       
        self.Name = name
        self.Fach = Studium
        self.Matrikel = Nr
2. Die Klasse in der ein Signal definiert und auf einen Slot connected wird:

Code: Alles auswählen

class Foo(QObject):

    Sig = pyqtSignal(Student)       # Definiert die Art meines Signals
    print str(type(Sig)) + " sig"      # Signalart wird ausgegeben
    
    def __init__(self, parent=None):# Konstruktor
        QObject.__init__(self, parent)
        
        self.Connection_SAS()       # Verbindet Signal&Slot

    
    def Connection_SAS(self):       #Verbindung von meinem Signal zur Ausgabe/Slot
        self.Sig.connect(self.Ausgabe)
        print type(self.Sig)

    
    def Sende(self, student):       #Sendet Signal
        self.Sig.emit(student)
       
    @pyqtSlot(Student)
    def Ausgabe(self, student):     #Ausgabe
        print student.Name + " " + student.Fach
3. Der main() aufruf:

Code: Alles auswählen

def main():                         # Main
    Andi = Student("Andi", "MB", 23523)

    Test = Foo()
    Test.Sende(Andi)




if __name__ == '__main__':
    main()
Was ich jetzt wissen möchte ist, wie ich das Signal an die Klasse binde also das jede Klasse sein eigenes Signal erzeugt.
Also ich schätze das liegt an dieser Zeile:

Code: Alles auswählen

Sig = pyqtSignal(Student)
Wenn ich allerdings:

Code: Alles auswählen

self.Sig = pyqtSignal(Student)
schreibe funktioniert der Code nicht. Dann kennt der Interpreter Sig nicht....
Ich hab mir dann gedacht das es so funktionieren könnte:

Code: Alles auswählen

#......code....
def Define_Signal():
     self.Sig = pyqtSignal(Student)
und dann hätte ich den Methodenaufruf in den Konstruktor geschrieben. Im Prinzip analog zur Connect_SAS()-Methode.
Allerdings funktioniert das auch nicht und deswegen bräuchte ich nochmals etwas Hilfe.
Was ich noch dazu sagen möchte ist, dass ja in meinen Code "Sig = ..." nicht an die Klasse gebunden ist. Allerdings schreibe ich (im Code kurz drunter) "self.Sig.emit(student)" wo ich ja dann doch self schreibe, was ja für mich gebunden heißt.... Wo liegt jetzt der Zusammenhang zwischen beiden Signal? Sind diese unterschiedlich?
Hast du(oder jemand anderes) nen Tipp wie da die Syntax funktioniert sodass ich ein Signal binden kann?

Danke soweit und einen schönen Tag

mfg Cascoin
BlackJack

@Cascoin: In dem Beispiel sollte alles wie gewünscht funktionieren. Da wird nämlich das Signal auf dem Exemplar aufgerufen. Du bringst hier aber wieder die Begriffe Klasse und Exemplar durcheinander.

Bei 1. sollen zum Beispiel nicht die Klasse `Student` gesendet werden, sondern Exemplare der Klasse. Also nicht der ”Bauplan” für Stundenten, sondern ganz konkrete Studenten.

Die Frage bei 3. müsste lauten wie man ein (gebundenes) Signal an jedes *Exemplar* bindet. Die Antwort ist: Gar nicht, dass macht die `QObject.__init__()` schon für Dich für jedes ungebundene Signal auf der Klasse. Man muss da also nicht mehr für machen als das Signal an die Klasse zu binden und in der `__init__()` die `QObject.__init__()` aufrufen.

Lass Dir mal nicht den `type()` ausgeben sondern `Sig` und `self.Sig` direkt.
Antworten