Zeilenpuffer als Interface zwischen unabhängigen Modulen

Code-Stücke können hier veröffentlicht werden.
Antworten
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hallo,

immer wieder taucht das Problem auf, unterschiedliche Module miteinander kommunizieren zu lassen, z.B. eine Anwendung und ein GUI-Modul. Jetzt hab ich mit eine Linebufferklasse erstellt, die das Ganze vereinfacht.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
    Modul:          Linebuffer
    Description:    Linebufferobject as interface for autarc modules,
                    for example GUIs and Applications and logfiles
    Version:        0.2
    Copyright:      2004 by Fritz Cizmarov fritz@sol.at
    Created:        16. Aug. 2004
    Last modified:  18. Aug. 2004
    License:        free
    Requirements:   Python2.3
    Exports:        Line
"""

from array import array


class Line(object):
    """ 
    Buffer for textlines, written to recievers line by line
    
    Line([initvalue,] [reciever1, [reciever2, [...]]] [encoding="utf-8"])
    return a linebufferobject initialized by initvalue with any
    number of recievers.
    Recievers can be filelike objects (have a 'write' function) or
    functions that can handle instances of Line or str.
    """
    __slots__ = ["__content", "__buffer", "__recievers", "__encoding"]

    def encoding(self):
        return self.__encoding
    encoding = property(encoding, doc="encoding of Linebuffer")

    def __init__(self, *args, **kw):
        self.__content = ""
        self.__buffer = array('c')
        self.__encoding = kw.pop("encoding", "utf-8")
        self.__recievers = list(args[1:])
        if args:
            self.write(args[0])

    def __del__(self):
        if len(self.__buffer):
            self.flush() 

    def __str__(self):
        """ return last line as string """
        return self.__content

    def __unicode__(self):
        """ return last line as unicode """
        return self.__content.decode(self.encoding)

    def write(self, s):
        """
            write s to Linebuffer, if s contains linefeeds, split
            s into lines and write them line by line
        """
        if s:
            if isinstance(s, unicode):
                s = s.encode(self.encoding)
            lfs = s.count('\n')
            if lfs > 1 or (lfs == 1 and s[-1] != '\n'):
                lines = s.split('\n')
                for line in lines[:-1]:
                    self.write(line+"\n")
                self.write(line)
            else:
                self.__buffer.fromstring(s)
                if s[-1] == '\n':
                    self.flush()
     
    def flush(self):
        """ send linebuffer to recievers and clear linebuffer """
        self.__content = self.__buffer.tostring()
        del self.__buffer[:]
        for reciever in self.__recievers:
            if hasattr(reciever, "write"):
                encoding = getattr(reciever, "encoding", self.encoding)
                if encoding and encoding != self.encoding:
                    data = unicode(self).encode(encoding)
                else:
                    data = self.__content
                reciever.write(data)
                if hasattr(reciever, "flush"):
                    reciever.flush()
            else:
                try:
                    reciever(self)
                except TypeError:
                    reciever(str(self))
        
    def from_iterable(self, iterable):
        """ get lines from iterable, iterable may also be a file """
        for line in iterable:
            if line[-1] != "\n":
                self.write(line+"\n")
            else:
                self.write(line)
    
    def add_reciever(self, reciever):
        """ add an reciever """
        self.__recievers.append(reciever)

    def remove_reciever(self, reciever):
        """ remove an reciever """
        self.__recievers.remove(reciever)
    
    
if __name__ == "__main__":
    from time import sleep
    import sys
    
    log = file("Linebuffer.log", 'w')
    l = Line("Dies ist ein Test!\n", sys.stdout, log,
             encoding=sys.getfilesystemencoding())
    l.write("Hallo ")
    l.write("Welt!")
    l.write("\n")
    l.write(u"öäüß\n")
    for c in "1234567890\n":
        l.write(c)
        l.flush()
        sleep(1)
    log.close()
Line ist eine Dateiartige Klasse, deren Instanzen mittels der Methode 'write' Zeichenketten übergeben werden können, die dann Zeilenweise an Empfänger weitergeleitet werden. Im Beispielcode am Ende des Scripts, wird z.B. sys.stdout als Empfänger definiert dem verschiedene Strings zugeführt werden, genausogut kann auch in einer GUI z.B. der Statuszeile von einer Applikation die Ausgabe mitgeteilt werden.
Mittels Lineobjekten ist auch die Übersetzung von Zeichenketten eines Encodings in ein anderes möglich.


Gruß

Dookie
Zuletzt geändert von Dookie am Freitag 20. August 2004, 01:48, insgesamt 1-mal geändert.
[code]#!/usr/bin/env python
import this[/code]
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Update des Scripts, das Beispiel schreibt jetzt gleichzeitig ein Logfile.


Dookie
[code]#!/usr/bin/env python
import this[/code]
mawe
Python-Forum Veteran
Beiträge: 1209
Registriert: Montag 29. September 2003, 17:18
Wohnort: Purkersdorf (bei Wien [Austria])

Hi Dookie!

Ich steh etwas auf der Leitung :oops:
Dookie hat geschrieben: in einer GUI z.B. der Statuszeile von einer Applikation die Ausgabe mitgeteilt werden
Kann ich die Statuszeile nicht direkt ansprechen, oder gibts Situationen wo das nicht geht? Oder versteh da irgendwas falsch? (Wie Du siehst steh ich wirklich auf der Leitung :?)

Könntest Du vielleicht ein Beispiel mit einer GUI zeigen, wo man die Line-Klasse in Aktion sieht (ein ganz einfaches, damit ich's auch versteh :wink:).

Gruß, mawe
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi mawe,

es geht dabei darum daß bei jeder GUI (Tk, wx, gtk, ...), der Text in der Statuszeile als Beispiel mit anderen Methoden gesetzt wird, Normalerweise muss dann bei verwendung einer anderen GUI überall in der eigentlichen Applikation geschaut werden, wo was wie zu ändern ist. Mit dem LineBuffer hast Du eine Schnittstelle, die wie eine Datei funktioniert, und du brauchst nur im GUI-Modul eine entsprechende Funktion, eventuell mit lambda, haben, die dann mit der Zeile aus dem Linebuffer die Ausgabe macht.

Hier noch ein Beispiel, daß stdout umbiegt und neben der Ausgabe von print auf der Console das ganze auch in ein Logfile speichert. Ich hab dazu einfach den Beispielcode im Modul etwas geändert.

Code: Alles auswählen

if __name__ == "__main__":
    from time import sleep
    import sys
    
    log = file("Linebuffer.log", 'w')
    sys.stdout = Line("Dies ist ein Test!\n", sys.stdout, log,
             encoding=sys.stdout.encoding)
    print "Hallo ",
    print "Welt!",
    print "\n",
    print u"öäüß\n" # Unicode wird in eingestelltes encoding umgewandelt
    for c in "1234567890\n":
        print c,
        sys.stdout.flush()
        sleep(1)
    log.close()
Ein Beispiel mit einer einfachen GUI werd ich heute abend mal zeigen.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Erstmal, ich hab das Linebuffermodul nochmal ergänzt, da war noch ein kleiner Fehler drinn bei der Zuweisung von self.__recievers in __init__ muss es heissen:
self.__recievers = list(args[1:])


So hier mal ein Beispiel mit einer GUI.

zuerst eine einfache Anwendung namens PWGen:

Code: Alles auswählen

import sys
import random

class PWGen:
    def __init__(self, chars="abcdefghijklmnopqrstuvwxyz", n=8, out=sys.stdout):
        self.chars = chars
        self.n = n
        self.out = out

    def next(self):
        self.out.write("".join(random.sample(self.chars, self.n))+"\n")

if __name__ == "__main__":
    PWGen().next()
Dies ist ein einfacher Passwortgenerator, der ein zufälliges Passwort in eine Datei oder nach sys.stdout schreibt.

Hier jetzt eine einfache Tkinter-GUI dazu:

Code: Alles auswählen

import sys

from Linebuffer import Line
from PWGen import PWGen

from Tkinter import *


class GUI:
    def __init__(self, func):
        self.root = Tk()
        self.msgwidget = Message(self.root, text="")
        self.msgwidget.pack()
        self.button1 = Button(self.root, text="Next", command=func)
        self.button1.pack()

    def write_msg(self, msg):
        """ Bekommt eine Zeile und gibt sie im Messagewidget aus """
        if not isinstance(msg, basestring):
            raise TypeError("%s ist kein String!" % msg)
        self.msgwidget.configure(text=msg)
    
    def mainloop(self):
        self.root.mainloop()
    

line = Line() # Linepuffer erstellen
pwgen = PWGen(out=line) # Passwortgenerator erstellen, Ausgabe in Linepuffer
gui = GUI(pwgen.next) # Gui erstellen Funktion = naechstes Passwort
line.add_reciever(gui.write_msg) # Empfaenger fuer Linepufferausgaben
gui.mainloop()
Man sieht, die Anwendung braucht nichts über die GUI zu wissen, sondern nur die Ausgaben in Dateiartige Objekte (mit einer write(text) methode) zu machen. In der GUI wird dann eine Funktion/Methode erstellt, die als Reciever für das Lineobjekt dient und die Ausgaben in das entsprechende Widget übersetzt.

Ich hoffe die Anwendung von Linebuffer wird damit etwas klarer.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
mawe
Python-Forum Veteran
Beiträge: 1209
Registriert: Montag 29. September 2003, 17:18
Wohnort: Purkersdorf (bei Wien [Austria])

Hi Dookie!

Erstmal vielen Dank daß Du Dir die Mühe gemacht hast noch ein Beispiel zu schreiben, nur weil irgendjemand (ich :oops:) das nicht sofort kapiert.
Dookie hat geschrieben: Ich hoffe die Anwendung von Linebuffer wird damit etwas klarer.
Schon, nur was genau ist der Vorteil gegenüber z.B. dieser Methode:

Code: Alles auswählen

import sys
import random

class PWGen:
    def __init__(self, chars="abcdefghijklmnopqrstuvwxyz", n=8):
        self.chars = chars
        self.n = n

    def next(self):
        return "".join(random.sample(self.chars, self.n))+"\n"

if __name__ == "__main__":
    print PWGen().next() 

Code: Alles auswählen

import sys

from PWGen import PWGen
from Tkinter import *

class GUI:
    def __init__(self,gen):
        self.root = Tk()
        self.gen = gen
        self.msgwidget = Message(self.root, text="")
        self.msgwidget.pack()
        self.button1 = Button(self.root, text="Next", command=self.write_msg)
        self.button1.pack()

    def write_msg(self):
        msg = self.gen.next()
        if not isinstance(msg, basestring):
            raise TypeError("%s ist kein String!" % msg)
        self.msgwidget.configure(text=msg)
   
    def mainloop(self):
        self.root.mainloop()
   
pwgen = PWGen()
gui = GUI(pwgen) 
gui.mainloop()
Hier weiß die Anwendung auch nichts von der GUI.
(Bitte entschuldige wenn ich Dich nerve :wink:)

Gruß, mawe
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi mawe,

die Vorteile sind bei so einem kleinen Beispiel noch nicht offensichtlich, da hier auch noch nicht gegeben.
Aber lass mal die Ausgabe gleichzeitig nach sys.stdout machen und in ein Logfile, ist mit dem Line-Objekt kein Aufwand, da brauchen nur noch die entsprechenden Reciever gesetzt werden. Oder die Funktion gibt einen String in Unicode aus und die GUI erwartet z.B. latin-1, hier kann die Umwandlung dem Lineobjekt übertragen werden. Du kannst auch einfach stdout durch ein Lineobjekt ersetzen und so alle print Anweisungen an die GUI umleiten.
Ausserdem habe ich, bei meinem PyXO festgestellt, daß das Laufzeitverhalten von Anwendungen die Strings zurückgeben, welche dann erst zusammengepfriemelt werden, bevor sie irgendwie ausgegeben werden, eher schlecht ist, gegenüber Anwendungen die die Ausgaben in Dateiartige Objekte machen. Auch kann von Line eine Klasse abgeleitet werden, die dann eine Filterfunktion beinhaltet um z.B. Farb-informationen oder sonstiges für die GUI zu übersetzen.
Oder du willst eine GUI für eine Anwendung, die ihre Statusmeldungen in ein Logfile schreibt machen. Jetzt lässt du die Meldungen einfach in ein Lineobjekt schreiben und sie erscheinen in der Statuszeile der GUI, gleich aufbereitet im passenden Encoding. Das war die Grundidee fürs Linebuffer-Modul.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
mawe
Python-Forum Veteran
Beiträge: 1209
Registriert: Montag 29. September 2003, 17:18
Wohnort: Purkersdorf (bei Wien [Austria])

Hi Dookie!

Hast recht, das Ding ist ziemlich praktisch.
Dookie hat geschrieben: Auch kann von Line eine Klasse abgeleitet werden, die dann eine Filterfunktion beinhaltet um z.B. Farb-informationen oder sonstiges für die GUI zu übersetzen.
Cool :D

Danke für die Antwort.

Gruß, mawe
Antworten