Diagramm Wrapper für Algorithmus

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
Poseidonius
User
Beiträge: 63
Registriert: Montag 23. Januar 2006, 08:58

Hallo zusammen,

ich möchte eine grafische Darstellung über einen kleinen Algorithmus hängen. Der Programmcode für das Diagramm soll aber nicht mit der Berechnung so verwickelt sein, dass ich die bunten Bildchen immer mit an Board habe (vielleicht weil auf dem Rechner gerade kein passendes grafisches Tool installiert ist). Daher habe ich eine Wrapperklasse "VizWrapper" zwischen die Klasse zur Darstellung und die für die Berechnung geschoben und von dort aus rufe ich eben die Darstellungsmethoden auf (oder lasse die Methoden eben leer). Da die grafische Darstellung mit show() in einer Endlosschleife verschwindet habe ich einen Thread angelegt, um die beiden Programmteile zu entkoppeln. Das Figure und die Axes Objekte habe ich als statische Objekte angelegt, weil ich die VizWrapper Methode analog in anderen Klassen des Programms einbauen möchte, aber immer die Ausgabe ins gleiche Diagramm produziert werden soll.

Leider ist die Anwendung schrecklich langsam und nur alle paar Sekunden gibts einen neuen Punkt im Diagramm.

Was mache ich falsch? Nach welchem Ansatz bekommen richtige Projekte ihre GUI verpasst? Bin als Neuling für alle Anregungen, Kritiken und Kopfschütteln dankbar

Ein schönes Wochenende

Poseidonius

Code: Alles auswählen

import threading
import random
import time
from pylab import figure, show


class Visualization(threading.Thread):
    fig = figure()
    ax = fig.add_subplot(111)    
    
    def __init__(self):
        threading.Thread.__init__(self)
        self.start()
        
    def run(self):
        print "Thread runs"
        show()
            
    def input_(self, value):
        Visualization.ax.plot(1, value, 'go')

    def output(self, value):
        Visualization.ax.plot(1, value, 'go')

class VizWrapper(Visualization):
    def __init__self(self):
        Visualization.__init__(self)
        
    def viz_output(self, value):
        print "viz_output"
        self.output(value)
        Visualization.fig.canvas.draw()

    def viz_input(self, value):
        print "viz_input"
        self.input_(value)
        Visualization.fig.canvas.draw()

class calc(VizWrapper):
    def calc_something(self):
        while 1:
	  time.sleep(0.2)
	  value=random.random()
	  self.viz_input(value)
	  # irgendwelche Berechnungen
	  self.viz_output(value)

cal=calc()
cal.calc_something()
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich kenne das von Dir verwendete Modul nicht, aber vielleicht mal ein paar Denkanstöße:

- Mit Threads sollte man im Zusammenhang mit GUI-Libs immer vorsichtig sein, da sich das meist beißt. Oftmals gibt es da eine eigene Thread-Klasse, die sich in das entsprechende Toolkit bzw. dessen main-Loop gut einfügt. Vielleicht recherchierst Du da mal entsprechend, um das zu verifizieren oder als Ursache ausschließen zu können?

- Diese Wrapper-Klasse ist imho unnötig! Deine "Berechnungen" solltest Du einfach in einem separaten Modul unterbringen. Diesem kannst Du dann ja eine main() mit CLI-Parametern verpassen. Somit kann man dann dieses "core"-Modul nutzen, wenn man keine GUI-Elemente haben kann oder möchte.

- Dein bisheriger Code wird so zum GUI-Aufsatz auf deiner Core-Lib und kann separat gestartet werden. Innerhalb dieses Moduls importierst Du einfach dein "Berechnungs"-Modul und rufst die passenden Funktionen, im Zusammenhang zum ersten Punkt (Threading in GUIs), auf.

Sollte die Berechnungen sehr klein sein, so kannst Du natürlich auch alles in ein Modul packen, und dann im CLI einen Schalter einbauen, mit dem man die GUI-Darstellung auswählen kann. Allerdings musst Du dabei dann die import-Anweisungen entsprechend mit try...except ImportError (?) umgeben, falls das Modul auf einem Rechner nicht präsent ist.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Poseidonius
User
Beiträge: 63
Registriert: Montag 23. Januar 2006, 08:58

Hallo,

zweiter Anlauf, diesmal nicht mit einer Matplotlib Diagramm sondern mit einer Qt Ausgabe und unter Berücksichtigung der Anmerkungen von Hyperion, um die Frage besser diskutieren zu können.

Ich habe eine main() Applikation, die eine GUI mit einer Textausgabe anlegt und eine "Simulationsklasse", die periodisch Daten produziert. Damit diese entweder auf der Kommandozeile, in der Qt GUI oder sonstwo landen habe ich eine Klasse dazwischen gesteckt - VizWrapperGUI() oder VizWrapper() - , die das Handling der Ausgabe übernimmt. Nun hatte Hyperion angemerkt, dass die Wrapperklasse unnötig sei, ... ich habe leider keine Idee, wie man es anders umsetzen könnte und wäre für Eure Anregungen dankbar.

Grüße aus Mageburg

Poseidonius

Code: Alles auswählen

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

import threading
import random
import time
import sys

from PyQt4.QtGui import QApplication, QMainWindow, QTextEdit

# Wrapper fuer GUI Ausgabe
class VizWrapperGUI():
    def __init__(self, textedit):
      self.textedit=textedit
  
    def viz_output(self, value):
	self.textedit.append("Messung %f"%value)
    
    def viz_allert(self):
      self.textedit.append("Alarm !!!")

# Alternativer Wrapper ohne GUI
class VizWrapper():
    def __init__(self, textedit=[]):
      pass
  
    def viz_output(self, value):
      print("Messung %f"%value)
    
    def viz_allert(self):
      print ("Alarm !!!")

# Sensor Simulation - generiert einen "Messwert" alle n Sekunden
class SensorSim(VizWrapperGUI, threading.Thread):

    def __init__(self, outputInterface):
        VizWrapperGUI.__init__(self, outputInterface)
	threading.Thread.__init__(self)
        self.start()

    def run(self):
	while 1:
	  time.sleep(0.2)
	  value=random.random()
	  if value>0.5:
	    self.viz_allert()
	  else:
	    self.viz_output(value)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainwindow = QMainWindow()
    textedit = QTextEdit(mainwindow)
    mainwindow.setCentralWidget(textedit)
    mainwindow.show()
    
    interface=SensorSim(textedit)
    sys.exit(app.exec_())
BlackJack

@Poseidonius: IMHO versucht Du da etwas mit Vererbung zu lösen was keine ist. Eine Vererbung ist eine "ist ein(e)"-Beziehung. Die Frage ob eine `SensorSim` ein Spezialfall einer `VizWrapperGUI` ist, würde ich aber ziemlich klar verneinen.

Du solltest Deine wertegenerierende Funktion vielleicht besser so schreiben, dass man ihr eine Funktion oder ein Objekt beim Aufruf mitgeben kann, über welches die Werte nach aussen kommuniziert werden können.

Code: Alles auswählen

def sensor(data_callback, alert_callback):
    while True:
        time.sleep(0.2)
        value = random.random()
        if value > 0.5:
            alert_callback()
        else:
            data_callback(value)
Statt der zwei Funktionen ginge natürlich auch ein Objekt mit zwei entsprechenden Methoden.

Bei Threads und GUIs musst Du vorsichtig sein -- die meisten GUI-Toolkits mögen es nicht wenn man aus einem anderen Thread als dem, in dem die GUI-Hauptschleife läuft, auf GUI-Elemente zugreift.

Zumindest bei dem einfachen Beispiel ist es auch nicht nötig von `Thread` zu erben. Man kann mit der `Thread`-Klasse auch Funktionen asynchron starten. Und wenn man Vererbung verwendet, dann sollte man nicht im Konstruktor den Thread starten. Dadurch wird es schwieriger die Klasse durch Vererbung erneut zu spezialisieren, weil ein Aufruf der `__init__` den Thread startet, man *danach* in der abgeleiteten Klasse nichts mehr mit dem Ergebnis der Initialisierung machen kann, *bevor* der Thread los läuft.
Poseidonius
User
Beiträge: 63
Registriert: Montag 23. Januar 2006, 08:58

Hallo BlackJack,

vielen Dank für Deine Anmerkungen! Mit der Vererbung hast Du natürlich recht. Kannst Du bitte noch einen Link posten, der einen asynchronen Funktionsaufruf unter Python zeigt, in der Doku konnte ich dazu nichts finden ....

Danke

Grüße

Poseidonius
BlackJack

@Poseidonius: Die Argumente `target`, `args`, und `kwargs` in `Thread.__init__()` sind zum starten von beliebigen aufrufbaren Objekten. Das ist in der Doku bei `Thread.run()` beschrieben.
Antworten