Funktionen innerhalb einer Klasse

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
FloKnoe
User
Beiträge: 4
Registriert: Donnerstag 18. Februar 2021, 08:45

Servus,
ich bin noch sehr neu in der Python Welt. Und heute ist mir ein unerklärlicher Fehler (für mich zumindest :D ) aufgefallen. Wie kann es sein wenn ich bei wInit.signals.process.connect(dreck) dreck aufrufe es funktioniert und wenn ich self.dreckB aufrufe es nicht mehr funktioniert? Es ist nicht der ganze Code nur ein Teil. Ich benutzte Python 3.7 auf Windows.

Code: Alles auswählen


class Worker(QRunnable):
    '''
    Worker thread
    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function

    '''
    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress

    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''
        # Retrieve args/kwargs here; and fire processing using them
        try:
            print("try")
            result = self.fn(*self.args, **self.kwargs)            
        except:
            print("except")
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))            
        else:
            print("else")
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            print("finally")
            self.signals.finished.emit()  # Done


def dreck():
    print("AAAA")


class MainWindow(QMainWindow):

wInit = Worker(self.KM.InitConnection)       
wInit.signals.progress.connect(dreck)

    def dreckB(self):
        print("BBBB")
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

die Frage ist, wieso das:
'wInit.signals.progress.connect(self.dreckB)'
nicht funktioniert?

Weil die Klasse 'Worker' keine Funktion 'dreckB' hat.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
FloKnoe
User
Beiträge: 4
Registriert: Donnerstag 18. Februar 2021, 08:45

Dennis89 hat geschrieben: Freitag 26. Februar 2021, 12:12
Weil die Klasse 'Worker' keine Funktion 'dreckB' hat.
Danke für deine schnelle Antwort!
Auch wenn ich in der Klasse 'Worker' die Funktion 'dreckB' deklariere, geht es leider nicht.

Code: Alles auswählen


class Worker(QRunnable):
    '''
    Worker thread

    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function

    '''

    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress

    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''
        # Retrieve args/kwargs here; and fire processing using them
        try:
            print("try")
            result = self.fn(*self.args, **self.kwargs)            
        except:
            print("except")
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))            
        else:
            print("else")
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            print("finally")
            self.signals.finished.emit()  # Done

    def dreckB(self):
        print("BBBB")
        
class MainWindow(QMainWindow):
	wInit = Worker(self.KM.InitConnection)       
        wInit.signals.progress.connect(self.dreckB)      
        
        
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Diese gefrickelte Fehlerbehandlung solltest du sofort wegwerfen, weil sie so allumfassend ist, dass dir auch Programmierfehler abgefangen werden.

Dann ist das MainWindow falsch programmiert. Attribute der Klasse legt man im __init__ an, und da hat man auch Zugriff auf self.

Die WorkerSignals Klasse sieht auch wenigstens ungewoehnlich, schlimmstenfalls falsch aus. Warum hast du so eine Zwischenklasse, statt auf dem Worker die Signale so wie sonst auch zu deklarieren?

progress_callback ist auch kein callback. Sondern ein Signal.

Und dann machst du auch noch grobe Schnitzer bei der Verbindung der Signale. Offensichtlich soll das ganze ja mit QThread zusammenarbeiten. Dafuer ist es aber zwingend notwendig, dass die signal/slot-connections "QueuedConnections" sind. Was du entweder explizit beim verbinden angeben musst, oder implizit durch die sogenannte thread-ownership bei Qt von dem Framework selbst erledigen lassen solltest.
owni
User
Beiträge: 5
Registriert: Freitag 26. Februar 2021, 13:10

Hallo zusammen,

ich bin ein Kollege von FloKnoe.
Leider sind die bisherigen Antworten noch nicht wirklich zielführend gewesen, da sie nicht das vorhandene Problem behandeln.
Da wir beide SPS-programmierer sind, kann es natürlich schon sein, dass nicht alles perfekt strukturiert ist. Hier sind wir noch am lernen. Wir sind natürlich für Verbesserungsvorschläge dankbar und werden die uns zu Herzen nehmen. Allerdings wäre es schön erstmal das akute Problem zu verstehen, bevor man das nächste angeht.
Ich habe jetzt den Code nochmal auf ein minimales Beispiel zusammengestrichen um unser Problem richtig darzustellen. (Der Code ist jetzt ein lauffähgies Programm)

Die Klassen "WorkerSignals" und "Worker" haben wir nach langer suche aus entsprechenden Foren gefunden. Welche bei den ersten Versuchen auch funktioniert haben. Nur in Verbindung mit den Klassen/Objekten haben wir jetzt unerklärliche Probleme.

Zum Gesamtverständniss:
Das originale Programm beinhaltet im "__init__.py" den Aufruf der GUI und instanziert andere Objekte, welche für das Programm entscheidend sind.
Die KM(Kopplungsmaschine) erstellt einen TCP-Socket und soll im eigenen Thread per RECV auf Daten warten und bei Erhalt entsprechende Events im HauptThread ausführen. Dafür die SIGNALS.

__init__.py :

Code: Alles auswählen

# IMPORT System
import sys
import os
import logging
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import QtCore
from PyQt5.uic import loadUi
import KM



class WorkerSignals(QObject):
    '''
    Defines the signals available from a running worker thread.

    Supported signals are:

    finished
        No data

    error
        tuple (exctype, value, traceback.format_exc() )

    result
        object data returned from processing, anything

    progress
        int indicating % progress

    '''
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(int)    


class Worker(QRunnable):
    '''
    Worker thread

    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function

    '''

    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress

    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''
        # Retrieve args/kwargs here; and fire processing using them
        try:
            print("DEBUG: try")
            result = self.fn(*self.args, **self.kwargs)            
        except:
            print("DEBUG: except")
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))            
        else:
            print("DEBUG: else")
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            print("DEBUG: finally")
            self.signals.finished.emit()  # Done

            
def ausserhalb_func():
    print("DEBUG: ausserhalb_func")


class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        # Objekt Kopplungsmaschine instanzieren
        self.KM = KM.KM()

        # Threadpool erstellen
        self.threadpool = QThreadPool()
                
        # init Worker
        wInit = Worker(self.KM.InitConnection)       
        wInit.signals.progress.connect(self.innerhalb_func)
        self.threadpool.start(wInit) 
    
    def innerhalb_func(self):
        print("DEBUG: innerhalb_func")

    def innerhalb_func_2(self, s):
        print ("DEBUG: innerhalb_func_2")
        print(s)



if __name__ == '__main__':

    app = QApplication([])
    window = MainWindow()
    app.exec_()


KM.py :

Code: Alles auswählen

class KM():

    # -------------------- Main --------------------
    def InitConnection(self, progress_callback):    
        print ("DEBUG: InitConnection")
        progress_callback.emit(1)


Das Programm macht natürlich jetzt keinen wirklichen Sinn, da sämtliche Inhalte entfernt wurden.
Rein Ablauftechnisch ist es aber genau das Selbe.

Problem ist, dass die Zeile:
wInit.signals.progress.connect(self.innerhalb_func)
Die Funktion nicht ausführt, wenn die Zielfunktion in der "MainWindow" Klasse liegt und wie dargestellt aufgerufen wird.
Welches der Signale man verwendet (progress, Result, finished) ändert auch nichts an dem Problem.

Die Console zeigt beim Ausführen folgendes:
CONSOLE:
DEBUG: try
DEBUG: InitConnection
DEBUG: else
DEBUG: finally

Ändert man die Funktion, welche beim dem Signal aus dem Thread aufgerufen wird auf eine Funktion, welche außerhalb aller Klassen liegt, funktioniert der Aufruf:
wInit.signals.progress.connect(ausserhalb_func)
Die Console zeigt beim Ausführen folgendes:
CONSOLE:
DEBUG: try
DEBUG: InitConnection
DEBUG: else
DEBUG: finally
DEBUG: ausserhalb_func
Allerdings wollen wir natürlich in der Klasse "MainWindow" weiter arbeiten, da dort die entsprechenden Events ausgeführt werden sollen.

Es gibt bestimmt noch zich andere Möglichkeiten um das Thread-Handling in den Griff zu bekommen. Allerdings suchen wir hier schon seit einer Woche vergebens. Diese Art über die Worker-Klasse war bislang der erste Versuch der Funktioniert hat, aus dem laufenden Subthread im Mainthread anzutriggern.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Meine Anmerkungen sind zum grossteil keine Fragen von Asthetik. Sondern von grundlegenden Missverstaendnissen und Fehlern sowohl was Python im allgemeinen, als auch Nebenlaeufigkeit in Qt im speziellen. Da jetzt eine Diskussion anzustrengen, das waere ja wohl auch auf spaeter zu verschieben, ist das, was hier nicht zielfuehrend ist. Ich erwaehne das alles nicht aus Jux und Dollerei.

Und mein Hinweis aus dem letzten mal war schon ernst gemeint: schmeisst diese ueberbordende Fehlerbehandlung weg. Denn jetzt fangt ihr euch jeden Fehler weg, Programmierfehler inklusive. Zwar wird die exception per sys.exc_info geholt, aber dann via einem nicht verbundenen signal einfach ins Nirvana geschickt.

Ich kann leider im Moment keine PyQt5 Anwendung laufen lassen, der core-dumped mir mit Problemen im plattform-Plugin. Aber ich bin zu 99.999% sicher, dass eure Aufrufsemantik des callbacks verbockt ist. Und einfach einen Fehler wirft. Der im Nirvana landet, und euch verwirrt zuruecklaesst.

Und danach kommt dann der naechste Fauxpax: wenn das geloest ist, dann wollt ihr offensichtlich Signale aus dem Worker an das Mainwindow schicken. Und diese Signale MUESSEN queued sein. Sind sie aber nicht. Womit es bestenfalls zu einer Warnung (und Weigerung) von Qt kommt. Schlimmstenfalls zu einem spaeter im Feld sporadisch auftretenden Segmentation-Fault. Weil man in der GUI absolut gar nichts manipulieren darf aus einem anderen Thread ausser dem Main-Thread.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

So, ich habe einen Weg gefunden das ohne GUI zu starten, dann core-dumped er nicht. Und ich habe das print("except") uebersehen. Das sollte natuerlich kommen, tut es aber nicht. Das Signal wird tatsaechlich uebergeben und nicht aufgerufen. Ich schaue mal, ob ich dazu was finden.
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

So, nach ein bisschen rumprobieren ist jetzt das Problem klar: euch fehlt bei der Ableitung von MainWindow der Konstruktoraufruf. Dadurch ist kein QObject initialisiert, und die Methode ein ungueltiger Slot.

Ein einfaches

Code: Alles auswählen

class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__()
loest das Problem.

Es bleibt bei meinen restlichen Anmerkungen. Das naechste Kofpkratzen ist sonst vorprogrammiert.

Code: Alles auswählen

# IMPORT System
import sys
import os
import logging
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import QtCore
from PyQt5.uic import loadUi



class WorkerSignals(QObject):
    '''
    Defines the signals available from a running worker thread.

    Supported signals are:

    finished
        No data

    error
        tuple (exctype, value, traceback.format_exc() )

    result
        object data returned from processing, anything

    progress
        int indicating % progress

    '''
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(int)



class Worker(QRunnable):
    '''
    Worker thread

    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function

    '''

    def __init__(self, fn, *args, **kwargs):
        super().__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()


    @pyqtSlot()
    def run(self):
        '''
        Initialise the runner function with passed args, kwargs.
        '''
        # Retrieve args/kwargs here; and fire processing using them
        print("DEBUG: try")
        result = self.fn(self.signals.progress)


def ausserhalb_func():
    print("DEBUG: ausserhalb_func")


class KM():

    # -------------------- Main --------------------
    def InitConnection(self, progress_callback):
        print ("DEBUG: InitConnection")
        print(progress_callback)
        print(dir(progress_callback))
        progress_callback.emit(1)



class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__()
        # Threadpool erstellen
        self.threadpool = QThreadPool()
        # Objekt Kopplungsmaschine instanzieren
        self.KM = KM()

        # init Worker
        wInit = Worker(self.KM.InitConnection)
        wInit.signals.progress.connect(self.innerhalb_func, Qt.QueuedConnection)
        wInit.signals.progress.connect(ausserhalb_func, Qt.QueuedConnection)
        self.threadpool.start(wInit)

    def innerhalb_func(self, foo):
        print("DEBUG: innerhalb_func")


if __name__ == '__main__':

    app = QApplication([])
    window = MainWindow()
    app.exec_()
owni
User
Beiträge: 5
Registriert: Freitag 26. Februar 2021, 13:10

Hallo __deets__,
vielen Dank erstmal für die Mühe. Den Code können wir erst am Montag wieder probieren.
Es scheint aber genau die Lösung für unser Problem zu sein.

Auch den abgespeckten Worker werden wir so übernehmen.

Zwei Sachen an dem Code sind mir allerdings noch nicht klar:
super().__init__() ->
euch fehlt bei der Ableitung von MainWindow der Konstruktoraufruf. Dadurch ist kein QObject initialisiert, und die Methode ein ungueltiger Slot.
Diese Erklärung verstehe ich leider nicht. Kann man dies einfacher erklären, damit ich das als Python-Anfänger verstehe oder kennen Sie eine Seite/Tutorial wo dieses Thema erklärt wird?

und

Code: Alles auswählen

wInit.signals.progress.connect(self.innerhalb_func, Qt.QueuedConnection)
Was bewirkt die Verwendung von: Qt.QueuedConnection ?

Vielen Dank
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wozu die queued connection ist, habe ich hier schon zweimal ausgeführt. Und die Qt Dokumentation ist ebenfalls sehr umfangreich.

Und super ist auch gut dokumentiert. Hast du die Dokumentation schon mal bemüht? Was daran ist unklar?
owni
User
Beiträge: 5
Registriert: Freitag 26. Februar 2021, 13:10

Hi,

leider stehe ich vor einem neuen "Problem", welchem ich aktuell nicht Herr werde.
Der Worker funktioniert einwandfrei. Jetzt habe ich aber eine Anforderung, dass ich weitere Parameter in die Funktion, welche im extra Thread läuft mit gebe.
Leider bekomme ich dies nicht zum laufen.

Hier wieder das Beispiel von oben mit der neuen Anforderung abgewandelt:

Code: Alles auswählen

# IMPORT System
import sys
import os
import logging
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import QtCore
from PyQt5.uic import loadUi



class WorkerSignals(QObject):
    '''
    Defines the signals available from a running worker thread.

    progress = pyqtSignal(int)



class Worker(QRunnable):

    def __init__(self, fn, *args, **kwargs):
        super().__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()


    @pyqtSlot()
    def run(self):
        result = self.fn(self.signals.progress)




class KM():

    # -------------------- Main --------------------
    def InitConnection(self, Signal, Data1, Data2):
        print ("DEBUG: InitConnection")
        print (Data1)
        print (Data2)
        Signal.emit(1)



class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__()
        # Threadpool erstellen
        self.threadpool = QThreadPool()
        # Objekt Kopplungsmaschine instanzieren
        self.KM = KM()

        # init Worker
        wInit = Worker(self.KM.InitConnection("TestData1", "TestData2")
        wInit.signals.progress.connect(self.innerhalb_func, Qt.QueuedConnection)
        self.threadpool.start(wInit)

    def innerhalb_func(self, foo):
        print("DEBUG: innerhalb_func")


if __name__ == '__main__':

    app = QApplication([])
    window = MainWindow()
    app.exec_()
Ich vermute ich muss in der "Worker" Class den Aufruf anpassen:
result = self.fn(self.signals.progress)

Leider bekomme ich trotz aller Versuche immer den Fehler, das ein Argument fehlt.

Danke im voraus.
owni
User
Beiträge: 5
Registriert: Freitag 26. Februar 2021, 13:10

Habe es jetzt doch hin bekommen.
Für alle die sich die selbe Frage stellen:

Code: Alles auswählen

# IMPORT System
import sys
import os
import logging
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5 import QtCore
from PyQt5.uic import loadUi



class WorkerSignals(QObject):
    '''
    Defines the signals available from a running worker thread.

    progress = pyqtSignal(int)



class Worker(QRunnable):

    def __init__(self, fn, *args, **kwargs):
        super().__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()


    @pyqtSlot()
    def run(self):
        result = self.fn(self.signals.progress, *self.args, **self.kwargs)




class KM():

    # -------------------- Main --------------------
    def InitConnection(self, Signal, Data1, Data2):
        print ("DEBUG: InitConnection")
        print (Data1)
        print (Data2)
        Signal.emit(1)



class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super().__init__()
        # Threadpool erstellen
        self.threadpool = QThreadPool()
        # Objekt Kopplungsmaschine instanzieren
        self.KM = KM()

        # init Worker
        wInit = Worker(self.KM.InitConnection, "TestData1", "TestData2")
        wInit.signals.progress.connect(self.innerhalb_func, Qt.QueuedConnection)
        self.threadpool.start(wInit)

    def innerhalb_func(self, foo):
        print("DEBUG: innerhalb_func")


if __name__ == '__main__':

    app = QApplication([])
    window = MainWindow()
    app.exec_()
Antworten