Seite 1 von 2

Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 10:20
von mutetella
Hallo,

in meiner Klasse 'Frame()' verwende ich eine Methode 'move()' zur Steuerung des Bildschirmcursors.

Code: Alles auswählen

    def move(self, step):
        '''Frame.move(step) -> (string, bool)

        'step' must be positive (moving forward), 
        negative (moving backward) integer or 
        zero (get current position).

        The first element of the returning tuple is a
        cursor positioning control sequence for moving
        the cursor forward if 'step' is a positive
        integer, backward if 'step' is a negative integer
        or leave the cursor on the current xy-values
        if 'step' is zero or the new coordinates were
        outside of the frame object.
        The second element of the returning tuple indicates
        whether the xy-values has changed (True) or not
        (False).'''
        new_y = step + self._y
        if new_y < 1 or new_y > self.cols * self.rows:
            new_y = self._y
        changed = self._y != new_y
        self._y = new_y
        return controls.MOVE_XYx.format(self.x, self.y), changed
Wie zu sehen ist gibt es ja Situationen, bei denen sich die Cursorposition nicht ändert. Meine 'move()' gefällt mir insofern nicht, dass durch die tuple-Rückgabe ständig so unschöne Abfragen wie

Code: Alles auswählen

move = frame.move(0)[0] 
#oder 
move, _ = frame.move(0)
im Code stehen, weil ich den 'changed'-Status nur an wenigen Stellen benötige.
Als Lösung fiele mir ein, der 'Frame()'-Klasse ein Attribut 'has_changed' zu spendieren, das ich jeweils auf 'True' oder 'False' setze.

Welche Möglichkeiten gäbe es noch, um so etwas 'schön' zu lösen?

mutetella

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 10:30
von cofi
Mit dem `has_changed` Attribut kommst du schnell zu Inkonsistenzen, es waere besser wenn du das als Property ausdruecken koenntest.

Alternative: Bau eine Funktion um `move` herum, die dir nur den 1. Wert gibt.

Aber ganz ehrlich: Das ist doch nur kosmetisch. Ich sehe kein Problem mit `move, _ = frame.move(0)`, solang das nicht _ueberall_ zu finden ist (die 1. Variante mit dem Index-Zugriff finde ich allerdings nicht so prickelnd).

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 10:33
von snafu
mutetella hat geschrieben:Welche Möglichkeiten gäbe es noch, um so etwas 'schön' zu lösen?
Man könnte zum Beispiel stattdessen eine Ausnahme werfen, wenn sich der Cursor nicht verändern konnte (etwa: `ValueError` oder davon abgeleitete eigene Exception).

Mindestens über Rückgabe des Tupels nach erfolgter Bewegung lässt sich streiten. Dass zum Erfahren der aktuellen Cursorposition ein `move(0)` aufgerufen werden soll bzw so dokumentiert ist, finde ich jedoch schon arg fragwürdig.

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 11:00
von mutetella
cofi hat geschrieben:Bau eine Funktion um `move` herum, die dir nur den 1. Wert gibt.
Hmm, versteh' ich jetzt nicht. Wie könnte das ausschauen?
cofi hat geschrieben:Aber ganz ehrlich: Das ist doch nur kosmetisch.
Ja, aber ich mag es nun mal, wenn die Dinge auch schön sind... :roll:

@snafu:
Die Idee der Exception finde ich gut, nur: 'move()' wird nach jedem Tastendruck aufgerufen, also bei einem guten Tipper sehr schnell, beim Einfügen von Text sehr, sehr schnell hintereinander. Kostet mich da ein 'try...except...' nicht zu viel?

'move(0)' ist wirklich etwas schräg, das werd' ich in eine separate Abfrage stecken. Man muss wissen, dass mein Vater schwäbischer Abstammung ist, ich fühl' mich da a bisl verschwenderisch, wegen solcher 'Kleinigkeiten' gleich eine eigene Methode zu schreiben... :P

mutetella

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 11:10
von EyDu
mutetella hat geschrieben:
cofi hat geschrieben:Bau eine Funktion um `move` herum, die dir nur den 1. Wert gibt.
Hmm, versteh' ich jetzt nicht. Wie könnte das ausschauen?

Code: Alles auswählen

def move2(self, step):
    return self.move(step)[0]
mutetella hat geschrieben:Die Idee der Exception finde ich gut, nur: 'move()' wird nach jedem Tastendruck aufgerufen, also bei einem guten Tipper sehr schnell, beim Einfügen von Text sehr, sehr schnell hintereinander. Kostet mich da ein 'try...except...' nicht zu viel?
Du machst dir bei so etwas tatsächlich Gedanken? Dein Rechner schafft sicher einige Milliarden Operationen pro Sekunde zu verarbeiten und du glaubst, dass du mit maximal 500 try-excepts an die Leistungsgrenze kommt :shock: Probiere es doch erstmal aus bevor du dir über Geschwindigkeit Gedanken machst ;-)

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 11:14
von snafu
Je nach Kontext reicht vielleicht auch eine Überprüfung an anderer Stelle mittels `if move(step) == old_position:` (unter der Voraussetzung, dass lediglich noch die Position zurückgeliefert wird). Ich weiß ja nicht, was dann passieren soll.

Aber an sich finde ich das mit der Ausnahme jetzt nich sooo abwegig... ;)

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 11:35
von snafu
Oder anders heraum (so macht es ja z.B. Qt an einigen Stellen): Die Methode `move()` liefert einen Wahrheitswert zurück der anzeigt, ob die Bewegung erfolgreich war. Möglicher Code beim Aufrufer wäre dann:

Code: Alles auswählen

if obj.move(step):
    pos = obj.position
else:
    handle_error()

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 11:51
von mutetella
@EyDu:
Ok, doch so einfach... ich glaubte, cofi meint etwas viel aufregenderes... :wink:
Aber eine Methode aufzurufen, nur um einen anderen Methodenaufruf zu verschönern...? Dann baue ich lieber 'move()' um...
EyDu hat geschrieben:Probiere es doch erstmal aus ...
Habe ich. Reines Tippen fällt da sicher nicht ins Gewicht, aber wenn ein Text eingefügt wird, machen weniger Abfragen und Schleifen durchaus einen Unterschied zwischen 'Text läuft herunter' und 'Text erscheint'...
snafu hat geschrieben:... vielleicht auch eine Überprüfung an anderer Stelle ...
An den Vergleich 'alte == neue' Position habe ich auch schon gedacht. Es geht letztlich darum, dass dann, wenn eine Cursorbewegung gefordert aber nicht ausgeführt wird, ein Scrollen stattfinden soll.
Eine Alternative könnte auch sein, dem frame-Objekt gleich die Scroll-Funktion zu übergeben, die dann ausgeführt wird. Ich würde das aber lieber in der Klasse lösen, in der das frame-Objekt verwendet wird.
snafu hat geschrieben:Die Methode `move()` liefert einen Wahrheitswert zurück...
Nachdem sich die aktuelle Position auf Dein Anraten hin nun über eine eigene Methode abfragen lässt, gefällt mir diese Idee sehr. Zu einem früheren Punkt lieferte mir 'move()' auch 'True/False', ich wollte einfach partout wieder mal alles in eine Methode stecken. Ich muss noch oft gegen die Wand laufen, bis ich mir das abgewöhn'... :oops:

mutetella

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 12:05
von snafu
BTW: Ich sehe, dass in der Doku im Eingangsbeitrag von dir steht, die neue Position würde als formatierter String ausgegeben werden. Das willst du dir sicher nochmal durch den Kopf gehen lassen, oder? :)

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 12:19
von deets
mutetella hat geschrieben:e]Habe ich. Reines Tippen fällt da sicher nicht ins Gewicht, aber wenn ein Text eingefügt wird, machen weniger Abfragen und Schleifen durchaus einen Unterschied zwischen 'Text läuft herunter' und 'Text erscheint'...
Natuerlich kann man zu langsam programmieren. Aber try/except kostet in Python nicht mehr als if/else, also kannst du es auch benutzen, wenn es semantisch mehr Sinn macht.

Und ich halte das fuer die bessere Alternative hier. Denn zu viel Code ist zu unrobust, weil jemand vergessen hat, Fehler die per Rueckgabewert kommuniziert werden auch auszuwerten.

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 12:21
von mutetella
@snafu:
Vereinfacht dargestellt hat ein Tastenanschlag bisher so ausgesehen:

Code: Alles auswählen

string_ = get_key() + frame.move(1)
write2stdout(string_)
Wenn mir ein frame-Objekt die Position als (x, y) übergibt, muss ich im Aufrufer daraus eine control sequence machen. Von meinem Gefühl her gehört das aber doch ins frame-Objekt. Oder warum nicht?

@deets:
Ok, ich dachte, irgendwo einmal die Info bekommen zu haben, dass 'try...except...' relativ teuer sei.

mutetella

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 12:29
von snafu
@mutella: Von *meinem* Gefühl her sollte sich das `Frame`-Objekt (oder zumindest dessen `.move()` bzw `.position`-Attribut) nicht um Dinge wie Übersetzungen in Terminalsequenzen kümmern. Das schränkt grundsätzlich die Wiederverwendbarkeit ein für Fälle, wo das Ausgabegerät kein Terminal ist oder wo die "Terminalsprache" eine andere ist (denk z.B. mal an die Windows-Konsole). Die Umwandlung von einem `(x, y)`-Tupel in eine Terminalsequenz lässt sich IMHO ohne großen Aufwand an anderer Stelle erledigen.

//edit: Wobei dir jetzt natürlich nicht zuviel auf einmal aufgebürdet werden soll, falls eine weitere Abstraktion größere Umbauten an deinem bestehenden Konzept erfordert. Ist erstmal nur als genereller Hinweis gemeint. ;)

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 12:59
von mutetella
@snafu:
Wobei ich diese Weiche eigentlich im frame-Objekt angedacht hatte. Eine Funktion oder Unterklasse muss sich demnach nicht darum kümmern, auf welche Art von Terminal geschrieben wird. Kann man darüber streiten?
snafu hat geschrieben:Wobei dir jetzt natürlich nicht zuviel auf einmal aufgebürdet werden soll,...
Nur her mit dem Cognac...

mutetella

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 13:37
von snafu
Hm, eine Vererbungshierarchie läuft doch normalerweise eher vom Allgemeinen ins Spezielle ab. Das Allgemeine wäre die Cursorverwaltung und das bedarfsweise Auslösen einer Scrollaktion. Das Spezielle ist die tatsächliche Umsetzung (z.B. in Form von Steuersequenzen, die ins Terminal geschrieben werden).

Du kannst es also entweder so machen, dass eine auf Unix-Terminalemulatoren (sprich: VT100) spezialisierte Klasse standardmäßig verwendet wird, die alles nötige implementiert (d.h. inklusive Übersetzungs- und Schreibvorgang). Oder du baust das `Frame`-Objekt als abstrakte Klasse, die ohne bestimmte Implementierungen für das Ausgabegerät gar nicht erst benutzt werden kann. Als weitere Möglichkeit möchte man vielleicht das `Frame`-Exemplar als Attribut an seinen "Terminalbeschreiber" hängen und die Trennung zwischen Verwaltung und Übersetzung auf diesem Weg vollziehen.

Die zuerst genannte Möglichkeit ohne jegliche Abstraktion oder Trennung ist sicher noch am unkompliziertesten sowohl in der Programmierung als auch in der Anwendung. Erweitern und umbauen kann man später immer noch. Ich würde aber auf jeden Fall, um es abgeleiteten Klassen einfacher zu machen, die Ausgabe der Position erstmal unformatiert machen, sodass sie auch in jedem anderen Kontext benutzt werden kann, ohne dass man einen String parsen müsste.

Das sind aber, wie so oft, persönliche Geschmacksfragen. Ich gliedere halt gern relativ kleinschrittig in Teilaufgaben, weil ich es schon oft in fremdem Code erlebt habe, dass ich eigentlich nur an eine Teilfunktionalität wollte, diese aber leider fest in einer großen Methode verankert war, sodass einem prinzipiell nur noch Copy&Paste bzw Nachbau übrig bleibt. Ich sag mir nämlich immer: Wiederverwendbarkeit ist nicht nur die Frage, ob es im eigenen Code mehrfach benutzt wird, sondern eher die theoretische Möglichkeit, *dass* man es könnte. Wobei man da, ohne Zweifel, aufpassen muss, es nicht zu übertreiben - schon klar.

Re: Ereignis abfragen. Wie soll/kann ich vorgehen?

Verfasst: Dienstag 6. März 2012, 13:44
von deets
@mutelle

vielleicht noch eine generelle anmerkung zu deinem methoden-design: die urspruengliche implementierung mit move(0) -> liefert position ist auesserst ungeschickt.

es gibt durchaus interessante apis, zb in jquery, die eine semantik haben die in etwa so geht:

- aufruf ohne argument -> rueckgabe von zustand.
- aufruf mit argument -> setzten des zustandes

also zb

position() -> (100, 200)
position(100, 200) -> None # oder was anderes sinnvolles, in jquery ueblicherweise "self" zum chainen.

deine api aber macht da diverse kardinale fehler: du waehlst als sentinel-wert einen wert aus der domaene deiner zu setzenden zustaende, und du hast *kein* default-argument!

ersteres schraenkt dich unnoetig ein: wenn du zB das ergebnis von move durch eine berechnung erhaeltst, und dabei kommt raus, dass ein zeichen *ueberschrieben* werden soll, also der offset legitimer weise 0 waere (mag konkret nicht sinnvoll sein, aber es gaebe ja faelle wo das so ist), dann musst du umstaendlich unterscheiden, dass das jetzt gar nicht passieren soll.

und zum zweiten sehen veraendernde und status meldende calls fuer den entickler und code-versteher gleich aus - beides mal ein argument.

besser also waere eine implementation, die 0 akzeptiert (und dann halt nix macht), und ein default-Wert von None, der signalisiert: "jetzt position zurueckgeben".

das ist im uebrigen eine der grossen staerken dynamischer typisierung wie in python: du kannst ueberall einen summentypen bilden aus einem datentyp (zB int) und irgendwas anderen (zB None), ohne dass du das deklarieren musst. in C/C++ geht das nicht, da arbeitet man dann nervend mit pointern oder muss extra argumente einfuehren.