Vorgehensweise, um Ausgabestring zu bilden / "einzusammeln"

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
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hallo,

innerhalb einer Schleife werden Tastenanschläge entgegengenommen. Handelt es sich z. B. um ein darstellbares Zeichen, wird im einfachsten Fall ein String

Code: Alles auswählen

char + control sequence
gebildet und an stdout geschickt.
Das Zeichen und die Sequenz werden an unterschiedlichen Orten gebildet.
Vereinfacht sieht das so aus:

Code: Alles auswählen

    def _run(self):
        with Terminal():
            key = ''
            while not key in self.exit_keys:
                _write2stdout(self._collector)
                key, displayable = get_key()
                if displayable:
                    self.echo(key)
                else:
                    getattr(self, key, lambda: None)()
        return ''.join(self.content)
Handelt es sich um ein darstellbares Zeichen, wird dieses an 'echo()' weitergereicht. Dort wird dieses Zeichen an den string 'self._collector' addiert und weitere Methoden zur Cursorpositionierung evtl. Scrolling etc. aufgerufen. Jede dieser Methoden addiert weitere Kontrollsequenzen zu 'self._collector', der dann innerhalb der Schleife zur Ausgabe an stdout geschickt wird.

Dasselbe geschieht auch, wenn ein handler, hier vereinfacht über 'getattr(...)()' dargestellt, aufgerufen wird. Dabei handelt es sich dann meist um Methoden, die Sequenzen zur Cursorpositionierung an 'self._collector' addieren.

Würdet ihr das auch so machen oder gibt es 'bessere' Lösungen? Ich dachte schon über 'Queue' nach, kann da aber eigentlich keinen Nutzen erkennen...

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@mutella: Das sieht alles sehr verworren für mich aus und leider gibst du auch etwas wenig Infos zum eigentlichen Zweck des Codes an. Zudem setzt du - wie mir scheint - viele Zusammenhänge als für jeden Leser sofort ersichtlich voraus, die aber eigentlich doch nicht so trivial sind (oder ich bin einfach zu begriffstutzig heute).

Ich versuche jetzt einfach mal, den Ablauf mit eigenen Worten darzustellen: Wenn die Eingabe also kein "Beenden-Code" war, wird irgendwas internes in Stdout geschrieben (sicher dass du deinen `collector` nicht eher `buffer` nennen willst?), dann wird der nächste Key geholt und wenn dies ein anzeigbares Zeichen war (IMHO besser: `printable`), dann wird `echo()` aufgerufen. Vom `echo()` würde ich jetzt eher erwarten, dass dort das Beschreiben stattfindet, aber offenbar befüllt das erstmal den Puffer, damit dieser im nächsten Durchlauf vom Aufrufer zum Schreiben benutzt werden kann (ganz schön magisch, das Ganze). Wenn es sich hingegen um einen nicht anzeigbaren Tastendruck gehandelt hat (nehme mal an, sowas wie Pfeil-Nach-Links), dann wird der Puffer nur mit der passenden Steuersequenz befüllt. Am Ende wird schließlich der Inhalt von `self.content` (vermutlich eine Liste) zurückgegeben. Fraglich nur, woher dieser Content plötzlich kommt und warum eine `run`-Funktion diesen zurückgibt, wo sie doch irgendwie schon vorher Ausgaben in Stdout geschrieben hat...?

Nun ja. Ich würde außerdem die "Anzeigbarkeit" nicht durch `get_key()` ausgeben lassen (die "Wahrheitswert-Tupel" haben es dir wirklich angetan, was? ;)), sondern eher sowas in der Art machen:

Code: Alles auswählen

key = get_key()
if is_printable(key):
    [...]
Anstatt `while not key in exit_keys` empfiehlt sich übrigens ein `while key not in exit_keys`. Das findet man öfter in "echtem" Code und es liest sich auch schöner. Der Effekt bleibt natürlich der selbe. Ich weiß auch nicht so recht, ob man diese Prüfung an den Beginn der Schleife setzen sollte oder ob man dies nicht lieber testen will, wenn der Key nicht "printable" war. Das hängt aber sicher auch vom genaueren Kontext ab...

Okay, dieser Beitrag war von mir jetzt auch etwas unstrukturiert, aber vielleicht konnte ich ja trotzdem ein paar Denkanstöße liefern... :)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@snafu:
Ja, so in etwa läuft das ab.
snafu hat geschrieben:... wird irgendwas internes in Stdout geschrieben (sicher dass du deinen `collector` nicht eher `buffer` nennen willst?), ...
Eigentlich wird da nicht 'irgendwas internes' geschrieben sondern das, was 'collector' nach (ok, das eigentliche Schreiben sollte nicht am Anfang der Schleife stehen) einem Tastendruck bekommt. Beim Eintritt in die Schleife ist 'self.collector' noch ein leerer string. Den Namen habe ich gewählt, weil eben in verschiedenen Methoden strings erzeugt werden und von 'collector' quasi eingesammelt und an stdout gereicht werden. Ich finde Deinen Vorschlag 'buffer' aber auch viel besser, das ändere ich.
snafu hat geschrieben:Vom `echo()` würde ich jetzt eher erwarten, dass dort das Beschreiben stattfindet, aber offenbar befüllt das erstmal den Puffer, damit dieser im nächsten Durchlauf vom Aufrufer zum Schreiben benutzt werden kann (ganz schön magisch, das Ganze).
'echo()' schreibt 1. den getippten Buchstaben in 'self.content', die Liste, die letztlich den getippten/eingefügten Text enthält und 2. wird dieser Buchstabe dann samt der Sequenz für einen Cursorschritt nach rechts zu 'self._buffer' (vormals 'collector') addiert.

Ok, das mit 'get_key()' habe ich deshalb so gelöst, weil die Art der Taste (anzeigbar oder nicht) dort bekannt ist. Das dann außerhalb nochmal aufs neue zu prüfen wäre ja doppelt gemobbelt. Aber ich schau' mir das nochmal an und denke darüber nach...
Warum ich immer 'while not...' oder 'if not...' verwende erschließt sich mir auch nicht... ;-)

Ich hoffe, der Ablauf ist soweit klar... bleibt die Frage: Gibt es zu meiner Vorgehensweise mit 'self._buffer' Alternativen?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nochmal kurz zu der Tupel-Thematik (zu deiner Kernfrage habe ich grad keinen Vorschlag parat): Zwar ist es in Python möglich, innerhalb von sequentiellen Datenstrukturen unterschiedliche Typen als Elemente zu verwenden (außer bei Strings natürlich), aber meiner Meinung nach sollte sowas möglichst vermieden werden und stattdessen ein gleichbleibender Typ verwendet werden. Gerade dein Vorgehen, den Zustand bzw die Art eines Objektes mittels `(obj, boolean)` kenntlich zu machen, ist doch recht "ungewöhnlich". Das kann man wie gesagt auch durch eine explizite Prüfung des Aufrufers lösen oder indem man eine Klasse einführt, die etwas über die Art aussagt (etwa: `obj.type`) - das würde ich in deinem Fall, wo nur "dies oder das" möglich ist, aber eher nicht machen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@snafu:
Ok, *zähneknirsch*... Im Fall von 'get_key()' lässt sich auf die Rückgabe von (obj, bool) sicher verzichten, aber: ... 2 Stunden später ... nun ja, jetzt wollte ich Dir eigentlich beschreiben, dass es für den Rückgabewert (int, bool) bei mindestens 2 meiner Funktionen keine Alternative geben kann. Kann es aber, schade eigentlich... :wink:

Allerdings verstehe ich noch nicht, warum man das nicht machen soll. Ok, bei meiner 'move()'-Methode im anderen Thread leuchtet mir das ein, das arbeitet letztlich runder... Aber beispielsweise bei 'get_key()': Sollte ich das nicht machen, weil man das halt nicht macht oder was ist der eigentliche Grund?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
BlackJack

@mutetella: Ich würde mal sagen das sind redundante Informationen. Im ersten Element steckt die Information ob es druck-/darstellbar ist, implizit drin. Und vielleicht nicht jeder Aufrufer will diese Information auch nochmal gesondert haben, bekommt sie aber zwangsweise immer mitgeliefert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@mutella:
Mir erscheint Deine Vorgehensweise umständlich und zugleich unübersichtlich. Warum hängt an jedem druckbaren Zeichen eine Steuersequenz? Was macht 'write2stdout'? Das wird ja für jeden while-Durchgang mit dem Collector aufgerufen. Wofür brauchst Du dann das 'echo()'?
Ich werd das Gefühl nicht los, dass Du hier etwas von hinten durch die Brust erreichen willst.

Dein Grundproblem ist ja, dass Du das Terminal nicht im Zeilenmodus vorliegen hast und daher nicht dessen Zeilenumbruch- und Editierfunktionalität + entsprechendes Cursorhandling nutzen kannst. Nun musst Du Dich zunächst fragen, welche dieser Funktionen für Dich bzw. Dein Modul weiterhin wichtig sind und ob diese nicht den Einsatz von curses/urwid rechfertigen würden.
Desweiteren musst Du die Eingabe sinnvoll verarbeiten und evtl. Terminalsteuersequenzen von Programmsteuerung (zB. Pfeiltasten) und einfachen Druckzeichen trennen, worum es in Deinem Post ja geht. Daher mein Vorschlag:
- implementiere Standardverhalten für alle Zeichen, z.B. nix wird ausgegeben (wäre Standardverhalten für Terminal im noecho-Modus)
- ermögliche Bindung von Aktionen an bestimmte Zeichen, z.B. Pfeiltasten bewegen den Cursor
Hierüber kannst du selbst die Ausgabe der darzustellenden Zeichen regeln, da dies ja nichts anderes als eine Ausgabeaktion Deines Programmes ist. Grundidee dabei ist, dass das Verhalten, welches ein Zeichen auslöst, mit den Zeichen verheiratet ist und Du nicht verschiedenste Aktionen in globalen Datenstrukturen explizit mitführen musst (wie Du es jetzt gerade tust). Solltest Du auf kompliziertere Terminalsequenzen angewiesen sein, kannst Du dieses Modell in einen Zustandsautomaten überführen (KA wieviel Cursormagie Du machen musst, ein hinreichend komplexes Cursorhandling würde diesen Schritt rechtfertigen)

Vllt. hilft Dir dieser Denkanstoß weiter.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@jerch:
Ich vermute, meine Vorgehensweise ist gar nicht so weit von dem entfernt, was Du mir vorschlägst. Allerdings reicht es leider nicht aus, z. B. aus einem 'a' ein 'a\x1b[1C' zu machen, da ich ja am Ende der Zeile in eine neue springen bzw. am Ende des Frames ein Scrollen einleiten muss. Ich denke mal, mit Zustandsautomaten meinst Du dieses ganze Zeug?
Wenn ja, dann ist das gerade der Punkt, an dem ich in den Seilen hänge. Einen übersichtlichen, nachvollziehbaren Ablauf (Zustandsautomat klingt super!!) zu schreiben, der Cursorbewegungen und davon abhängende Aktionen wie Scrollen oder Textverschiebungen verarbeitet. Hänge da momentan in der Luft... ;-(
jerch hat geschrieben:... ob diese nicht den Einsatz von curses/urwid rechfertigen würden.
Ich möchte meinen Kalender (ja, den gibt es noch... nein, ich bin noch nicht fertig) von der Kommandozeile aus abfragen allerdings zur Eingabe etwas mehr Komfort haben als das 'raw_input()'/'input()' bieten. Allein dazu das gesamte Terminal an curses oder urwid zu übergeben gefällt mir nicht. Ein Zwischending eben...

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@curses:
Es geht auch ohne curses, nur bist Du dann halt gezwungen, die Keycodes für Sondertasten und benötigte Steuersequenzen selbst mitzuführen, was schnell dazu führt, dass Du die halbe curses-DB nachbaust. Um diese Fleissarbeit zu umgehen, könntest Du zumindest das curses-Modul nutzen, um die benötigten Flags und Steuersequenzen abzufragen (tigetflag, tigetnum, tigetstr, tparm).

@Automat:
Hast Du denn schon was modelliert? Ich kann Dir da ohne weiteres Wissen um die Funktionalität nicht wirklich weiterhelfen, da das die Komplexität des Automaten entscheidend beeinflusst. Z.B. könnte man hier Eingabemasken mit farblichen Hervorhebungen wollen, am besten mit einer Templatesprache ;)
Grundsätzliche Vorgehensweise wäre, zunächst alle möglichen Zustände, -übergänge und Aktionen zu sichten und zu optimieren. Hast Du alle Zustände mit gültigen Übergängen definiert, musst Du das nur noch runterprogrammieren.
Antworten