pyMorse - Text -> Morsecode und Morsecode -> Text

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich habe "aus Spaß" ein Modul geschrieben, was Text nach Morsecode "übersetzt" und auch Morsecode nach Text. Interessanterweise gibt es - zumindest im Cheese-Shop - dazu nur ein Modul, welches aus einer Zeile Code und einen Dict für Mapping besteht... Das Modul kann auch nur Text -> Morse, nicht umgekehrt.

Der Code ist hier:
http://bazaar.launchpad.net/~noisefloor ... pymorse.py

Die Docstrings sind vollständig.
Getestet habe ich bis jetzt "nur" mit Python 2.7.
Verbesserungsverschläge sind willkommen :-)

Eine Frage direkt: würde ihr die print-Anweisung auch so verwenden oder hier lieber auf's Logging-Modul zurückgreifen?

Gruß, noisefloor
BlackJack

@noisefloor: Bei den Ausnahmen hätte ich eine Klassenhierarchie erstellt. Mindestens mal eine Basisklasse von der alle anderen abgleitet werden, damit man nicht gezwungen ist wirklich alle in einem ``except`` aufzuführen wenn man nur daran interessiert ist grundsätzlich zu wissen ob es fehlgeschlagen ist.

Du machst alles möglich an Zeichen konfigurierbar, nur nicht den kurzen und den langen Pieps selbst.

Die Schleife am Ende der `__init__()` würde auf Klassenebene nur einmal ausgeführt werden und nicht bei jedem Erstellen eines `Morse`-Exemplars.

Die ``print``\s hätte ich in der Tat über Logging gelöst, dann kann man sie abschalten.

``else: pass``?

Wenn man bei verknüpften Bedingungen ein bisschen auf die Reihenfolge achtet kann man ab und zu Rechenzeit sparen. Also zum Beispiel erst prüfen ob man im `strict_mode` ist und dann erst die Daten untersuchen. Und das ganze auch so anordnen, dass man nicht mehrfach das gleiche ausrechnen muss und/oder in ``elif``-Zweigen teilweise die gleiche Bedingung stehen hat wie im Test für den Zweig davor. Beispiel:

Code: Alles auswählen

        if not test(data) and strict_mode:
            error()
        elif not test(data) and not strict_mode:
            warning()
        # 
        # Besser:
        # 
        if not test(data):
            if strict_mode:
                error()
            else:
                warning()
Ist von den Bedingungen her IMHO auch einfacher verständlich ausgedrückt.

Statt den `KeyError` abzufangen hätte man anstelle des []-Zugriffs auch die `get()`-Methode verwenden können.

In `is_valid_morse_code()` könnte man `all()` und einen Generatorausdruck verwenden.

Der Rückgabewert von `is_valid_input_text()` ist unnötig kompliziert. Es würde reichen einfach die gefundenen ungültigen Zeichen zurück zu geben. Der Wahrheitswert ergibt sich daraus ja automatisch ist also redundant. Ein `set()` wäre hier vielleicht besser als eine Liste.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
BlackJack hat geschrieben:@noisefloor: Bei den Ausnahmen hätte ich eine Klassenhierarchie erstellt. Mindestens mal eine Basisklasse von der alle anderen abgleitet werden, damit man nicht gezwungen ist wirklich alle in einem ``except`` aufzuführen wenn man nur daran interessiert ist grundsätzlich zu wissen ob es fehlgeschlagen ist.
Verstehe gerade nicht, wie du das genau meinst... :( Kannst du das bitte ein bisschen ausführlicher darstellen?
BlackJack hat geschrieben:Du machst alles möglich an Zeichen konfigurierbar, nur nicht den kurzen und den langen Pieps selbst.
Das ist - im Moment - Absicht. Ich hatte darüber nachgedacht, aber für den Moment verworfen. Vielleicht baue ich das noch ein...
BlackJack hat geschrieben:Der Rückgabewert von `is_valid_input_text()` ist unnötig kompliziert. Es würde reichen einfach die gefundenen ungültigen Zeichen zurück zu geben. Der Wahrheitswert ergibt sich daraus ja automatisch ist also redundant.
Das stimmt... dann müsste die Funktion aber `is_invalid_input_text`heißen, weil ein Rückgabewert von `True` ja dann die nicht-Gültigkeit kennzeichnet :-) Werde ich aber so machen, dass ist besser als das Tuple, was im Moment zurückgegeben wird.

Gruß, noisefloor
BlackJack

@noisefloor: Ad Ausnahmeklassenhierarchie: Im einfachsten Fall eine `MorseError`-Ausnahme von der alle anderen erben. Damit man als Benutzer der Objekte eine einfache Möglichkeit hat alle Ausnahmen zu behandeln ohne tatsächlich alle hinschreiben zu müssen. Im Moment müsste man ja so etwas hier schreiben:

Code: Alles auswählen

    except (
        MorseCodeError, InvalidCharSeparatorError, InvalidWordSeparatorError
    ) as error:
        print 'Something went wrong:', error
Wo man eigentlich lieber das hier schreiben würde:

Code: Alles auswählen

    except MorseError as error:
        print 'Something went wrong:', error
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

aktualisierte Version ist im Repo.

Logging statt print ist nach wie vor nicht drin, kommt noch.

Frage - stelle mich da gerade blöd an: Wie bekomme ich den die überlangen Zeilen bei

Code: Alles auswählen

raise MorseError(...)
weg, _ohne_ dass `\n` in der Ausgabe mit angezeigt werden (wenn diese zwecks Zeilenumbruch in den Strings drin wären?

Gruß, noisefloor
BlackJack

@noisefloor: Schmeiss einfach die ganzen unsinnigen `repr()`-Aufrufe raus.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@noisefloor: was soll den der Quatsch in Zeile 282? So war das mit dem »get« von BlackJack nicht gemeint.
In Zeile 277: was soll die List-Comprehension?
In Zeile 275 produzierst Du zur Zeit einen NameError.

Code: Alles auswählen

return self.word_sep.join(
    self.char_sep.join(
        self.CHAR_TO_MORSE.get(char, self.missing_char_placeholder) for char in word)
    for word in text.split())
In Zeile 329 hast Du eine interessante Art »all« zu benutzen. Sobald »char_sep« oder »word_sep« aus mehreren unterschiedlichen Zeichen bestehen funktioniert die Methode auch nicht mehr.

Code: Alles auswählen

return morse_code.replace(self.word_sep,'').replace(self.char_sep,'').replace('.','').replace('-','')
»is_not_valid_input_text« hieße besser »find_invalid_characters« und wenn Du schon »set«s benutzt, dann konsequent:

Code: Alles auswählen

return ','.join(set(text)-set(self.CHAR_TO_MORSE)-set(' '))
Und in »_check_separators« würde ich auch die Schleifen weglassen:

Code: Alles auswählen

if set('-.')&set(self.char_sep): …
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

Danke für die Tipps. Habe jetzt soweit alles eingearbeitet (wenn ich nix übersehen habe...). Aktuelle rev4 ist im Repo.

Das Modul heißt jetzt übrigesn pyMorseCode, weil es schon pyMorse gibt (Link: http://www.openrobots.org/morse/doc/latest/pymorse.html). Hat zwar nix mit Morsen zu tun, hat aber den gleichen Namen...

logging statt print kommt noch, kann aber ein wenig dauern. Und die Docstrings muss ich noch mal prüfen.
Muss erst Mal ein paar produktive Sachen ;-) machen.

Gruß, noisefloor
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

die Launchpad-Projektseite ist jetzt auch umbenannt: http://launchpad.net/pymorsecode

Das Repo ist jetzt hier: https://code.launchpad.net/~noisefloor/ ... code/trunk

In der aktuellsten Version ist jetzt auch print durch's Logging-Modul ersetzt.

Wenn noch jemand Anmerkung oder Verbesserungsvorschläge hat -> bitte :-)

Gruß, noisefloor
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@noisefloor: für meinen Geschmack erzeugst Du in »from_morse« noch zu viele Listen:

Code: Alles auswählen

…
   def _char_from_morse_strict(self, char):
        try:
            return self.MORSE_TO_CHAR[char]
        except KeyError:
            raise MorseCodeError('Illegal morse code sequence',
                                 'Morse code contains a non-valid code sequence')

    def _char_from_morse_lazy(self, char):
        try:
            return self.MORSE_TO_CHAR[char]
        except KeyError:
            self.logger.warning('Found illegal morse code sequence {0}. '
                'Will use {1} as place holder instead'.format(
                    char, self.missing_morse_code_placeholder))
            return self.missing_morse_code_placeholder

    def from_morse(self, morse_code):
        '''args: morse_code
        This function translates the given morse_code "morse_code" back to
        text. Prior to translation, the function "is_valid_morse_code" is
        called to check whether the morse code contains valid characters for
        code representation only.
        In case "strict_mode" is set to True (the default), an exception will
        be raised. In case it is set to False, the class attribute
        "missing_morse_code_placeholder" will be used in the translated text
        for non-translatable sequences of morse code.
        '''

        self._check_separators()
        #test on invalid characters in morse code
        if self.is_valid_morse_code(morse_code):
            raise MorseCodeError('Morse Code error',
                                 'Morse code contains invalid characters')
        get_char = self._char_from_morse_strict if self.strict_mode else self._char_from_morse_lazy
        #decode morse code into characters 
        return ' '.join(
                ''.join(map(get_char, word.split(self.char_sep)))
            for word in morse_code.split(self.word_sep))
…
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@Sirius3: Danke für den Input - ist in weiten Teilen übernommen :-)

Dann wäre das Modul erst Mal fertig.

Habe hier doch eine Menge gelernt - auch wenn mein ursprünglicher Code im Prinzip das gleiche Resultat geliefet hat, ist die jetzige Version doch kompakter und eleganter.

Danke noch Mal an alle, die hier Tipps gegeben haben.

Gruß, noisefloor
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

Hallo,

Auch wenn du sagst, dass du nur diese ITU-R M.1677-1-Empfehlung (ich hoffe, ich habe sie richtig kopiert :D) unterstützt, wäre es doch eventuell trotzdem noch interessant, auch einen erweiterten Zeichensatz zu unterstützen, oder (Also Umlaute usw)?

Man könnte dann eventuell einen strengen Modus und einen erweiterten Modus unterstützen.

Außerdem könnte ich auch komplett anderen Morsecode für Japanisch liefern :)
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich hatte darüber nachgedacht, dann aber verworfen - eben weil's nicht standardisiert ist.

Außerdem gibt es auch noch den "alten" Morse-Code (sieht http://en.wikipedia.org/wiki/Morse_code ... nd_history. Da ist aber das Problem, dass die Null ein laaaaaaang ist ;-) und sich IMHO nicht wirklich mit den ASCII Zeichen abbilden lässt.

Gruß, noisefloor
Hellstorm
User
Beiträge: 231
Registriert: Samstag 22. Juni 2013, 15:01

noisefloor hat geschrieben:Hallo,

ich hatte darüber nachgedacht, dann aber verworfen - eben weil's nicht standardisiert ist.

Außerdem gibt es auch noch den "alten" Morse-Code (sieht http://en.wikipedia.org/wiki/Morse_code ... nd_history. Da ist aber das Problem, dass die Null ein laaaaaaang ist ;-) und sich IMHO nicht wirklich mit den ASCII Zeichen abbilden lässt.

Gruß, noisefloor
Hm, naja, aber ich finde ein unstandardisiertes, aber trotzdem unterstütztes Format doch besser. Das muss sich ja nicht ausschließen, notfalls könnte man doch auch mehrere Versionen anbieten :D Bzw. einfach das meistbenutzte Format nutzen.

Auch wenn pyMorseCode ja ganz gut ist, kommt mir das daher irgendwie ein wenig unvollständig vor. Etwas schade, wie ich finde.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

fühl' dich frei eine Patch einzureichen oder zu forken. ;-)

Die "Übersetzung" Morse-Code <-> Text läuft ja über ein Dict, d.h. ist einfach erweiterbar.

Gruß, noisefloor
Antworten