Übersetzer

Code-Stücke können hier veröffentlicht werden.
Antworten
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Vielleicht kann das wer gebrauchen mittlerweile hab ich irgendwie die Lust dran verloren. Dieses simples Übersetzungsprogramm nutzt die Wörterbücher von Ding läuft unter Python 2 und 3. Eine Online Suche(Fortschritt mit Eingabe) und die letzten Macken und Performanz Probleme könnten noch behoben werden. Auf meinen Rechner i3@2.2GHz braucht das Programm mind. 7s für das Einlesen.
Für Verbesserungsvorschläge bin ich offen.

Code: Alles auswählen

#!/usr/bin/env python

from __future__ import print_function
from sys import version_info, stdin, argv
from platform import system
from time import clock
     
__author__ = "Daniel Oelschlegel"
__copyright__ = __author__
__version__ = "0.01"
__license__ = "bsdl"

#FIXME: python 3 give a different dictionary size as python 2
#FIXME: empty queries return broken translations because of empty fields(?)

if version_info[0] >= 3:
    raw_input = input
    from _thread import start_new_thread
else:
   from thread import start_new_thread
   from itertools import izip as zip, imap as map
   
ENCODING = stdin.encoding if system() == "Windows" else "utf-8"

dict = {}

def trimmer(text):
    '''Remove all extra informations'''
    return text.split("[")[0].split("{")[0].strip()

def read_dictionary(file_name, start_time):
    '''Read out the dictionary file and save into global dictionary'''
    for line in open(file_name) if version_info[0] < 3 else open(file_name, encoding="utf-8"):
        #ignoring comments and invalid vocabulary entries
        if line.startswith("#") or line=="\n":
            continue
        
        #cloud definition
        left_side, right_side = map(lambda x: x.split("|"), line.split("::"))

        #split for alternative identifiers separated by semicolon
        for item in zip(left_side, right_side):
           for side_left in map(trimmer, item[0].split(";")):
                for side_right in map(trimmer, item[1].split(";")):
                    #save mapping in both search directions
                    for translation_pair in ((side_left, side_right, ">"), (side_right, side_left, "<")):
                        value = translation_pair[0].lower()
                        if value not in dict:
                            dict[value] = [translation_pair]
                        else:
                            dict[value].append(translation_pair[1:])
                        
    print("\ndictionary with %d word created in %.2f seconds" % (len(dict), clock() - start_time))
           
def output(value):
    '''Output encoding, a special thing under windows'''
    if isinstance(value, str) and system() == "Windows" and version_info[0] < 3:
        return value.decode("utf-8").encode(ENCODING)
    return value
 
def make_unicode_python2(value):
    '''Inputs for python 2 must convert to utf8'''
    #TODO: TEST REQUIRED UNDER LINUX
    return value.decode(ENCODING).encode("utf8") if version_info[0] < 3 else value
    
def gui():
    '''User interface with missing abort condition'''
    print("for exit press [ctrl]+[c]")
    while True:
        search_word_lower = make_unicode_python2(raw_input(": ").strip().lower())
        if search_word_lower in dict:
            item = dict[search_word_lower][0]
            print(item[0], ": %s%s" % (item[2], item[1]) , end="")
            for item in dict[search_word_lower][1:]:
                print(output(", %s%s")  % (item[1], item[0]), end="")
            print()
        else:
            print("%s not found" % search_word_lower)

def main(file_name):
    start_new_thread(read_dictionary, (file_name, clock()))
    ##read_dictionary(file_name, clock()) #DEBUG
    gui()

if __name__ == "__main__":
    print(__file__.strip(".py"), __version__, "by", __author__)
    main("dictionaries/de-en.txt" if len(argv) < 2 else argv[1])
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@darktrym: thread sollte man in Python 2 nicht benutzen, der Unterstricht für Python3 macht das noch viel deutlicher. Statt dessen solltest Du threading benutzen. »dict« ist ein builtin Datentyp und sollte nicht als Variablennamen verwendet werden. Statt eines normalen Dictionaries wäre hier ein defaultdict(list) das richtige. Zum »open« fehlt ein »close«. Die vier Verschachtelten for-Schleifen ließen sich wahrscheinlich sauberer lösen.
BlackJack

@darktrym: `__file__.strip(".py")` mag vielleicht funktionieren, sieht aber sehr danach aus als wenn die `strip()`-Methode nicht verstanden wurde.

Bei `trimmer()` frage ich mich gerade wie eine Eingabe ausehen muss, damit das erste `split()` Sinn macht.

Für meinen Geschmack wird im Code zu oft unterschieden welche Python-Version oder welche Plattform man gerade „bedient”. Beim öffnen der Textdatei könnte man zum Beispiel einfach grundsätzlich `io.open()` mit Kodierungsangabe verwenden.

Das die Startzeit als Argument in `read_dictionary()` hineingereicht wird ist IMHO eine komische API. Das hat mit der eigentlichen Funktion der Funktion doch überhaupt nichts zu tun. Das globale Wörterbuch ist unschön und so wie das dann von einem Thread befüllt wird, auch ein Fehler. Der Thread macht überhaupt keinen Sinn. Eigentlich müsste man ja warten bis das Wörterbuch geladen ist bevor man Abfragen macht. *Dann* kann man den Code aber auch einfach linear in einem Thread abarbeiten.

`output()` gibt überhaupt nichts aus. Kodierungen sind auch nichts spezielles unter Windows, das müsste man unter Linux oder jedem anderen Betriebssystem genau so machen.

`make_unicode_python2()` gibt gar kein Unicode zurück wie der Name vermuten lässt. Das sollte es vielleicht besser denn Text sollte man in Python 2 intern als Unicode verarbeiten. Dazu vielleicht auch besser eine `input()`-Funktion erstellen die das schon zurück gibt.
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Ich gehe mal auf einige Punkte ein:
Der Thread hat(te) lediglich die Aufgabe die Wartezeit zu verkürzen. Sicher nicht das sinnvollste aber sehr praktisch.
__file__.strip(".py") könnte man auch durch os.path.splitext(__file__)[0] ersetzen, stimmt schon.
Im Übrigen scheint io.open mit dem encoding Parameter ein anderes Verhalten zu haben unter Python 2 auf Eingaben von Wörtern mit Umlauten.
Zu den Punkt open muss man wieder schließen. Ich bin ehrlich gesagt davon ausgegangen das wird automatisch gemacht, da ich gar keinen File Handler mehr darauf habe.

Code: Alles auswählen

#!/usr/bin/env python

from __future__ import print_function
from sys import version_info, stdin, argv
from platform import system
from time import clock
from os.path import splitext
import io
     
__author__ = "Daniel Oelschlegel"
__copyright__ = __author__
__version__ = "0.01"
__license__ = "bsdl"

#FIXME: python 3 give a different dictionary size as python 2
#FIXME: empty queries return broken translations because of empty fields(?)

if version_info[0] >= 3:
    from _thread import start_new_thread
else:
   from thread import start_new_thread
   from itertools import izip as zip, imap as map
   
ENCODING = stdin.encoding if system() == "Windows" else "utf-8"

dictionary = {}

def trimmer(text):
    '''Remove all extra informations'''
    return text.split("{")[0].split("[")[0].strip()

def read_dictionary(file_name, start_time):
    '''Read out the dictionary file and save into global dictionary'''
    #for line in io.open(file_name, encoding="utf8"):
    for line in open(file_name) if version_info[0] < 3 else open(file_name, encoding="utf-8"):
        #ignoring comments and invalid vocabulary entries
        if line.startswith("#") or line=="\n":
            continue
        
        #cloud definition recognised with pipe symbol
        left_side, right_side = map(lambda x: x.split("|"), line.split("::"))

        #split for alternative identifiers separated by semicolon
        for item in zip(left_side, right_side):
           for side_left in map(trimmer, item[0].split(";")):
                for side_right in map(trimmer, item[1].split(";")):
                    #save mapping in both search directions
                    for translation_pair in ((side_left, side_right, ">"), (side_right, side_left, "<")):
                        value = translation_pair[0].lower()
                        if value not in dictionary:
                            dictionary[value] = [translation_pair]
                        else:
                            dictionary[value].append(translation_pair[1:])
                        
    print("\ndictionary with %d word created in %.2f seconds" % (len(dictionary), clock() - start_time))
           
def create_output(value):
    '''Output encoding differs from used platform, terminal'''
    if isinstance(value, str) and system() == "Windows" and version_info[0] < 3:
        return value.decode("utf-8").encode(ENCODING)
    return value
 
def universal_input(value):
    '''Retrieves the user input with given value prompt as utf8 string'''
    #TODO: TEST REQUIRED UNDER LINUX
    if version_info[0] < 3:
        return raw_input(value).decode(ENCODING).encode("utf8")
    
    return input(value)
    
def gui():
    '''User interface with missing abort condition'''
    print("for exit press [ctrl]+[c]")
    while True:
        search_word_lower = universal_input(": ").strip().lower()
        if search_word_lower in dictionary:
            item = dictionary[search_word_lower][0]
            print(create_output("%s: %s%s") % (item[0], item[2], item[1]) , end="")
            for item in dictionary[search_word_lower][1:]:
                print(create_output(", %s%s")  % (item[1], item[0]), end="")
            print()
        else:
            print(create_output("%s not found" % search_word_lower))

def main(file_name):
    ##start_new_thread(read_dictionary, (file_name, clock()))
    read_dictionary(file_name, clock()) #DEBUG
    gui()

if __name__ == "__main__":
    print(splitext(__file__)[0], __version__, "by", __author__)
    main("dictionaries/de-en.txt" if len(argv) < 2 else argv[1])
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Bei mir bricht das Programm in seiner zuletzt geposteten Form mit einem Fehler ab. Ich habe zuvor das Ubuntu-Paket `trans-de-en` installiert und nutze entsprechend die darin enthaltene Datei. Das Ganze rufe ich so auf:

Code: Alles auswählen

% python translator.py /usr/share/trans/de-en
translator 0.01 by Daniel Oelschlegel
Traceback (most recent call last):
  File "translator.py", line 92, in <module>
    main("dictionaries/de-en.txt" if len(argv) < 2 else argv[1])
  File "translator.py", line 87, in main
    read_dictionary(file_name, clock()) #DEBUG
  File "translator.py", line 41, in read_dictionary
    left_side, right_side = map(lambda x: x.split("|"), line.split("::"))
ValueError: too many values to unpack
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Ich bezieh's direkt von hier http://ftp.tu-chemnitz.de/pub/Local/urz ... /de-en.txt . Der Fehler hat sicher seine Ursache an den Leerzeilen.
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
BlackJack

@darktrym: Das mit dem `strip()` *muss* man durch etwas anderes ersetzen wenn man nicht so auf Fehler im Programm steht. Denn sonst muss man aufpassen wie man die Programmdatei nennt. Was ich als Fehler ansehen würde.

Was meinst Du mit anderem Verhalten? `io.open()` mit Kodierungsangabe dekodiert in Python 2 zu Unicode. Also genau das was `io.open()` in Python 3 an der Stelle macht. In wie fern sollte sich das anders verhalten? *Du* behandelst innerhalb des Programms Zeichenketten ständig unterschiedlich nach einer Prüfung welche Python-Version vorliegt. Genau diesen Unsinn sollte man mit `io.open()` ja loswerden. Es reicht also nicht grundsätzlich `io.open()` zu verwenden, Du musst natürlich auch aufhören die Werte in Python 2 wie Bytes zu behandeln.

Die Frage ist *wann* Dateien wieder geschlossen werden. Das sollte man immer explizit machen, denn sonst ist das Programmende der Zeitpunkt an dem vom Betriebssystem üblicherweise garantiert wird, dass die Datei geschlossen wird. Dateihandle sind allerdings eine endliche Ressource und bei Dateien die zum schreiben geöffnet wurden, ist erst beim Schliessen garantiert das auch alle Daten tatsächlich geschrieben wurden. Darum ist es unsauber Dateien nicht möglichst zeitnah wenn man sie nicht mehr benötigt zu schliessen.

Eine Leerzeile kann nicht zu „too many values to unpack” führen, denn die enthält ja buchstäblich nichts, so das beim Splitten auch keine Liste mit mehr als zwei Elementen entstehen kann.
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Also in Prinzip so:

Code: Alles auswählen

#!/usr/bin/env python

from __future__ import print_function
from sys import version_info, stdin, argv
from platform import system
from time import clock
from os.path import splitext
import io
     
__author__ = "Daniel Oelschlegel"
__copyright__ = __author__
__version__ = "0.01"
__license__ = "bsdl"

#FIXME: python 3 give a different dictionary size as python 2
#FIXME: empty queries return broken translations because of empty fields(?)

if version_info[0] < 3:
   from itertools import izip as zip, imap as map
   
ENCODING = stdin.encoding if system() == "Windows" else "utf-8"

dictionary = {}

def trimmer(text):
    '''Remove all extra informations'''
    return text.split("{")[0].split("[")[0].strip()

def read_dictionary(file_name, start_time):
    '''Read out the dictionary file and save into global dictionary'''
    with io.open(file_name, encoding="utf8") as dict_file:
        for line in dict_file.readlines():
            #ignoring comments and invalid vocabulary entries
            if line.startswith("#"):
                continue
            try:
                #cloud definition recognised with pipe symbol
                left_side, right_side = map(lambda x: x.split("|"), line.split("::"))
            except ValueError:
                continue
            #split for alternative identifiers separated by semicolon
            for item in zip(left_side, right_side):
               for side_left in map(trimmer, item[0].split(";")):
                    for side_right in map(trimmer, item[1].split(";")):
                        #save mapping in both search directions
                        for translation_pair in ((side_left, side_right, ">"), (side_right, side_left, "<")):
                            value = translation_pair[0].lower()
                            if value not in dictionary:
                                dictionary[value] = [translation_pair]
                            else:
                                dictionary[value].append(translation_pair[1:])
                            
    print("\ndictionary with %d word created in %.2f seconds" % (len(dictionary), clock() - start_time))
           
def create_output(value):
    '''Output encoding differs from used platform, terminal'''
    if isinstance(value, str) and system() == "Windows" and version_info[0] < 3:
        return value.decode("utf-8").encode(ENCODING)
    return value
 
def universal_input(value):
    '''Retrieves the user input with given value prompt as utf8 string'''
    #TODO: TEST REQUIRED UNDER LINUX
    if version_info[0] < 3:
        return unicode(raw_input(value).decode(ENCODING))
    
    return input(value)
    
def gui():
    '''User interface with missing abort condition'''
    print("for exit press [ctrl]+[c]")
    while True:
        search_word_lower = universal_input(": ").strip().lower()
        if search_word_lower in dictionary:
            item = dictionary[search_word_lower][0]
            print(create_output("%s: %s%s") % (item[0], item[2], item[1]) , end="")
            for item in dictionary[search_word_lower][1:]:
                print(create_output(", %s%s")  % (item[1], item[0]), end="")
            print()
        else:
            print(create_output("%s not found" % search_word_lower))

def main(file_name):
    read_dictionary(file_name, clock())
    gui()

if __name__ == "__main__":
    print(splitext(__file__)[0], __version__, "by", __author__)
    main("dictionaries/de-en.txt" if len(argv) < 2 else argv[1])
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@darktrym: ich seh jetzt noch, dass dicionary eine globale Variable ist :-( und readlines unnötig, da direkt über das Fileobjekt iteriert werden kann.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hab jetzt auch mal ne Version hingeklatscht. :)
http://pastebin.com/sVTKcecb

Hat noch viele Macken: Kann (unter Python 2.x) noch nicht mit Umlauten umgehen. Stellt keine alternativen Übersetzungen (also Begriffe mit ähnlicher Bedeutung) dar. Außerdem könnte man sicher auch JSON statt Pickle verwenden. Ich sag ja: Hingeklatscht. ;)
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@snafu: Deine Translator-Klasse sieht für mich reichlich seltsam aus. Man kann entweder ein Dictionary angeben oder es wird versteckt entweder ein Pickle-File oder eine Textdatei geladen. Die ganzen Lade- und Parse-Methoden haben nichts mit der Klasse zu tun. Warum sind es dann Klassenmethoden?
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

Danke nochmal an alle für's Drüberschauen.
Ich belass' das mal so. Die Reg. Ausdrücke nehmen sich in Hinsicht auf Lesbarkeit nicht viel, dafür sind sie um einiges langsamer.
Mit dem Pickle Modul war auch in einer meiner ersten Varianten, habe mich aber dagegen entschieden; das Benutzerverzeichnis ist schon zugemüllt genug.

Code: Alles auswählen

#!/usr/bin/env python

from __future__ import print_function
from sys import version_info, stdin, argv
from platform import system
from time import clock
from os.path import splitext
import io
#import re
     
__author__ = "Daniel Oelschlegel"
__copyright__ = __author__
__version__ = "0.01"
__license__ = "bsdl"

#FIXME: python 3 give a different dictionary size as python 2
#FIXME: empty queries return broken translations because of empty fields(?)

if version_info[0] < 3:
   from itertools import izip as zip, imap as map
   
ENCODING = stdin.encoding if system() == "Windows" else "utf-8"

def clean_entry(text):
    '''Remove all extra informations'''
    return text.split("{")[0].split("[")[0].strip()#re.sub(r'{.*?}|\[.*?\]', '', text).strip()

def read_dictionary(file_name, start_time):
    '''Read out the dictionary file and save into global dictionary'''
    dictionary = {}
    with io.open(file_name, encoding="utf8") as dict_file:
        for line in dict_file:
            #ignoring comments and invalid vocabulary entries
            if line.startswith("#"):
                continue
            try:
                #cloud definition recognised with pipe symbol
                left_side, right_side = map(lambda x: x.split("|"), line.split("::"))
            except ValueError:
                continue
            #split for alternative identifiers separated by semicolon
            for item in zip(left_side, right_side):
               for side_left in map(clean_entry, item[0].split(";")):
                    for side_right in map(clean_entry, item[1].split(";")):
                        #save mapping in both search directions
                        for translation_pair in ((side_left, side_right, ">"), (side_right, side_left, "<")):
                            value = translation_pair[0].lower()
                            if value not in dictionary:
                                dictionary[value] = [translation_pair]
                            else:
                                dictionary[value].append(translation_pair[1:])
                            
    print("\ndictionary with %d word created in %.2f seconds" % (len(dictionary), clock() - start_time))
    return dictionary       
           
def create_output(value):
    '''Output encoding differs from used platform, terminal'''
    if isinstance(value, str) and system() == "Windows" and version_info[0] < 3:
        return value.decode("utf-8").encode(ENCODING)
    return value
 
def universal_input(value):
    '''Retrieves the user input with given value prompt as utf8 string'''
    #TODO: TEST REQUIRED UNDER LINUX
    if version_info[0] < 3:
        return unicode(raw_input(value).decode(ENCODING))
    
    return input(value)
    
def gui(dictionary):
    '''User interface with missing abort condition'''
    print("for exit press [ctrl]+[c]")
    while True:
        search_word_lower = universal_input(": ").strip().lower()
        if search_word_lower in dictionary:
            item = dictionary[search_word_lower][0]
            print(create_output("%s: %s%s") % (item[0], item[2], item[1]) , end="")
            for item in dictionary[search_word_lower][1:]:
                print(create_output(", %s%s")  % (item[1], item[0]), end="")
            print()
        else:
            print(create_output("%s not found" % search_word_lower))

def main(file_name):
    gui(read_dictionary(file_name, clock()))

if __name__ == "__main__":
    print(splitext(__file__)[0], __version__, "by", __author__)
    main("dictionaries/de-en.txt" if len(argv) < 2 else argv[1])
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Sirius3 hat geschrieben:@snafu: Deine Translator-Klasse sieht für mich reichlich seltsam aus. Man kann entweder ein Dictionary angeben oder es wird versteckt entweder ein Pickle-File oder eine Textdatei geladen.
Stimmt. Sauberer wären statische Methoden für den `Translator` wie etwa `.from_pickle(filename)` und `.from_text(filename)`, die dann anstatt von `__init__()` benutzt würden, wenn man kein `dict`-Objekt übergeben möchte.
Sirius3 hat geschrieben:Die ganzen Lade- und Parse-Methoden haben nichts mit der Klasse zu tun. Warum sind es dann Klassenmethoden?
Klar, ein Ansatz über Funktionen wie `dict_from_pickle()` und `dict_from_text()`, die das zur Weiterverarbeitung benötigte Wörterbuch erzeugen, wäre auch gegangen. Den Ansatz über die o.g. statischen Methode finde ich persönlich allerdings schöner. Auch, wenn er nicht direkt etwas mit dem Übersetzungsvorgang zu tun hat.

Es steht dir ja im Übrigen völlig frei, mal eine alternative Variante zu posten, so wie du sie geschrieben hättest... ;)
Antworten