Ereignis abfragen. Wie soll/kann ich vorgehen?

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

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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

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).
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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

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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

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 ;-)
Das Leben ist wie ein Tennisball.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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... ;)
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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

@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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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

@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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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

@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
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

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

@deets:
Ok, ich versuche gerade zu verstehen, was Du meinst :wink: :

Mit 'Sentinelwert' meinst Du 'new_y', mit 'Domäne der zu setzenden Zustände' den Bereich 1 bis (cols * rows), oder? Falls ich das richtig verstanden habe verstehe ich den Fehler und das daraus resultierende Problem nicht... :?
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.
Ja, das Ergebnis von 'move()' kommt durch eine Berechnung zustande und es kann durchaus sein, dass 'move()' eine Position zurückgibt, die auf ein bereits gesetztes Zeichen verweist. Weshalb wäre der offset dann 0? Was meinst Du mit offset?
Ob das Zeichen auf dieser Position dann überschrieben oder dort etwas eingefügt wird entscheidet sich doch dort, wo 'move()' aufgerufen wird.


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

@mutella: Mit Sentinelwert ist wohl eher die 0 gemeint, da sie sich von allen anderen Werten unterscheidet und ihr eine besondere Funktionalität zukommt, wenn sie übergeben wird - nämlich die von dir dokumentierte bloße Rückgabe der Position. Sentinels werden meisten benutzt, um das Ende von Arrays zu markieren und sowas (Vgl. NULL).

Dass es sich um einen Sentinel handelt ist hier aber IMHO nicht ganz offensichtlich, zumal er sich ja mehr oder weniger zufällig aus der Verhaltensweise der Funktion ergibt. Denn die Rückgabe der Position würde ja auch bei jedem anderen Wert erfolgen. Das Alleinstellungsmerkmal der 0 ist hier halt, dass sie keine Positionsveränderung betreibt.

Als "Domäne der zu setzenden Zustände" würde ich - etwas weiter gefasst - nicht nur den tatsächlich möglichen Wertebereich sehen, sondern allgemein Werte vom Typ Ganzzahl. Hier würde wohl `None` besser passen und dies - wie von deets vorgeschlagen - als Defaultargument, damit ein Aufruf der Funktion ohne Argument zum "None-Verhalten" führt.

Und "Offset" schließlich dürfte als Verschiebung der Cursorposition (dein `step`) zu sehen sein. So ganz habe ich in diesem Teil aber auch nicht verstanden, was deets mit dem "Ergebnis von move() durch eine Berechnung" meint. Er denkt hier offenbar an Situationen, wo ein Zeichen überschrieben wird, aber der Cursor sich dadurch nicht nach rechts bewegt. Der Satz ist allerdings IMHO etwas wirsch geschrieben... ^^
deets

Also, die Domaene einer Funktion ist die Menge der Werte, die sie schluckt. Die Domaene von sin sind zB die reellen Zahlen (bzw. deren Abstraktion im Rechner durch doubles oder floats oder so, aber da wollen wir mal nicht so sein)

Und es ist halt immer bloed, wenn man *aus* dieser Menge ein Element nehmen muss, um eine Sonderbehandlung auszuloesen. Stattdessen ist es besser, eine neue Menge von Werten zu bilden (abstrakt gesprochen) - indem man die Summe aus der Domaene und einem Sentinel nimmt, in Python oft None.

Wenn ich also die Position abfragen will, dann sollte ich nicht einen Wert uebergeben muessen - darum den default-wert "None", der nicht in der Domaene liegt, den ich also einfach erkennen kann, und dann eine Sonderbehandlung ausfuehren:

Code: Alles auswählen

def position(self, pos=None):
      if pos is None:
         return self._position
      else:
         self._position = pos
deets

@snafu

Was ich mit "Berechnung der Position" meinte bezog sich darauf, dass man ja oft Code hat, der einen Parameter berechnet. Nehmen wir mal an, man haette ein kleines Spiel auf der Console, und ein Boesewicht wird durch einen Buchstaben (X) dargestellt. Wenn ich jetzt in jedem Zeitschritt die Aktion des X berechne, dann kann die KI vielleicht sagen "och, ich bleib mal stehen" - und damit ist das Argument von move() dann 0. Wenn das jetzt aber eine Sonderbehandlung im Sinne eines alternativen Codepfades ausloest - dann hat man ein Problem.

Das muss im konkreten nicht so sein, aber als generelles Design-Paradigma, und darauf bezogen sich ja meine Argumente.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hej, das war mir doch alles längst klar, aber ihr habt das super erklärt... 8)


Ok, vielen Dank für Eure Hilfe! Jetzt hab's auch ich geschnallt, tu' mir halt oft mit Begrifflichkeiten schwer, ich mach' mich dann mal an den Umbau...

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