python2to3

Du hast eine Idee für ein Projekt?
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hab jetzt einfach mal die komplette Formatierungs-Funktionalität geschrieben. Aufgrund der speziellen Anforderungen, die du an die Ausgabe stellst, sind es doch ein paar Zeilen mehr geworden. Manches kann man vermutlich noch besser machen, aber ich hoffe trotzdem, den Quelltext etwas verständlicher für die Allgemeinheit hinbekommen zu haben: https://gist.github.com/3527463

Wie du siehst, habe ich den Info-Text bei den Testdatensätzen stark gekürzt, damit das schon von dir genannte Problem der Überlänge nicht auftritt. Wobei man auch hier sein Terminal-Fenster im Vollbildmodus laufen lassen sollte.

Eine Implementierung mit passendem Zeilenumbruch kommt dann in der nächsten Maus (irgendwann). ;)

//edit: MIr wird gerade bewusst, für die Leerzeilen hätte man genau so gut ein simples "\n" nehmen können... :oops:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu,
boah ... da hast Du Dich aber ins Zeug gelegt, den Code muß ich erst mal in Ruhe studieren und testen.
Jetzt merke ich erst, in welchen Sphären Ihr schwebt, wenn ich Deinen Code so anschaue.
Kein Wunder, daß ich da immer einen auf den Deckel bekomme ... :lol:

Das mit den Leerzeilen und dem '\n', da hättest Du mich fragen können. :wink:

Ich war auch aktiv und habe an meinem Code weiter geschraubt.
Auch wenn dieser sich nicht mit Deinem Code vergleichen lässt, funktioniert er doch recht gut.
Ich habe diesen noch an einigen Stellen weiter gekürzt und mit zwei Funktionen erweitert.
- weitere Filtermöglichkeit der Suchergebnisse
Das ist besonders für mich wichtig, da ich als mit großen Datenmengen arbeite und ein Mehrfachfilter hier von Vorteil ist.
- Definition der Sucheingabe, berücksichtigen von Groß-/Kleinschreibung, oder die Umwandlung der Sucheingabe in Groß- oder Kleinbuchstaben
Da ich viel auch mit Dateien arbeite, wo nur Großschreibung besteht, ist dann das Arbeiten etwas einfacher. Ich gebe 'a' ein und 'A' wird gesucht.
Mit dieser Lösung, bin ich allerdings nicht ganz so glücklich. Besser wäre hier, wenn bei der Suche selbst Groß-/Kleinschreibung nicht berücksichtigt würde, also 'a = A, ä = Ä, ... usw.'
- Wiederholen der Kopfzeile alle x-Zeilen
Gerade bei größeren Datenmengen übersichtlicher.

Auch wenn mein Code nicht zu empfehlen ist, könnt Ihr Euch ein Bild machen, wie ich das eine oder andere (mit meinen Möglichkeiten) umgesetzt habe. https://gist.github.com/3531055
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu,
habe Deinen Code getestet, läuft wirklich prima. :D

Zum Testen, habe ich diesen Bereich für mich angepasst, um Suchergebnisse zu erhalten.

Code: Alles auswählen

def test():
    headings = headinfo
    entry = input('\nSuchbegriff: ')
    if entry:
        with open(datapool, 'r') as infile:
            reader = csv.reader(infile, delimiter="\t", quotechar="^")
            contents = [row for row in reader if any(entry in x for x in row)]
    if contents:
        print(get_formatted_table(headings, contents))
Werde Deinen Code noch weiter studieren, da noch nicht alles klar für mich ist.
Danach werde ich meine alten Code durch diesen ersetzen.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Sodele, Dank snafuś Input der mich insperiert hat, habe ich meinen Code überarbeitet und ziemlich gekürzt.
Dabei habe ich dier Funktion 'get_formatted_table' von snafu, mit leichten Änderungen übernommen.

Poste hier mal die aktuelle Version: https://gist.github.com/3554990

Ich hoffe mal, daß für Euch meine Bemühungen, ein bisschen sichtbar sind.
Für Kritik und weiteren Input von Euch, freue ich mich! :wink:
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

5 Sekunden drauf geguckt und das hier gefunden:

Code: Alles auswählen

def spellings(entry):
    if spelling == 1:
        entry = entry.upper()
        entry = entry.replace('[ä]', 'Ä')
        entry = entry.replace('[ö]', 'Ö')
        entry = entry.replace('[ü]', 'Ü')
    elif spelling == 2:
        entry = entry.lower()
        entry = entry.replace('[Ä]', 'ä')
        entry = entry.replace('[Ö]', 'ö')
        entry = entry.replace('[Ü]', 'ü')
    return entry
Das geht doch auch eleganter... Stichwort Datenstrukturen! ;-) Im Moment reines Copy&Paste...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

Die ganze Funktion erscheint mir merkwürdig. Was sollen die eckigen Klammern bei der Ersetzung? Gibt die jemand tatsächlich ein, also zum Beispiel 'L[ö]we aus [Ö]sterreich' und die Funktion macht dann 'LÖWE AUS [Ö]STERREICH' daraus‽ Wenn man einfach nur alles in Gross- oder Kleinbuchstaben wandeln möchte, dann reicht es auch tatsächlich mit *Zeichen* zu arbeiten, also `unicode` und `upper()` oder `lower()` aufzurufen:

Code: Alles auswählen

In [68]: print s
Löwe aus Österreich

In [69]: print s.upper()
LÖWE AUS ÖSTERREICH

In [70]: print s.lower()
löwe aus österreich
Das `spelling` aus diesem komischen `privatparam`-Modul ist auch eigenartig. Da könnte man sich mindestens mal an die Konvention halten Konstanten in GROSSBUCHSTABEN zu schreiben.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo Hyperion, hallo BlackJack,
5 Sekunden, hey da habe ich mich ja schon gesteigert, vorher war die Zeit ja noch kürzer. :wink:
Nein ehrlich, freue mich über Eure Kritik und daß Ihr mich noch nicht ganz aufgegeben habt!

Daß 'ä, ö, ü' auch bei upper/lower berücksichtigt werden, wußte ich nicht.
Ist ein klarer Vorteil für unicode und kürzt den Code bei 'spellings' erheblich.

Code: Alles auswählen

def spellings(entry):
    if spelling == 1:
        entry = entry.upper()
    elif spelling == 2:
        entry = entry.lower()
    return entry
Mit der Umsetzung der Funktion 'spellings', bin ich nicht ganz glücklich, ist momentan ein Kompromiss.
Ideal bei der Suche wäre die zu durchsuchenden Daten, in Kleinschreibung umzuwandeln. So könnte man die Sucheingabe selbst auch in Kleinschreibung eingeben und müßte nicht überlegen, wie evtl. das Datenoriginal aussieht.
Das lässt sich bestimmt umsetzen, muß mir noch überlegen, wie man das am Einfachsten machen kann.

Beim Stichwort 'Datenstrukturen', habe ich wohl noch einiges zu lernen. :wink:
Vielleicht ein kurzes Beispiel dafür?

Die Konstanten bei privatparam.py, habe ich entsprechend geändert (DATAPOOL, HEADINGS, HEADDISTANCE, LISTFILTER, SPELLING).
Das 'komische `privatparam`-Modul', soll dem Benutzer die Möglichkeit geben, gewisse Einstellungen vornehmen zu können.
Der Grund ist der, da ich versuche den Code flexibel zu halten, wäre es unnötig das Modul menu anzufassen.
Ich hoffe, daß die Erklärungen in den DOC-Strings, verständlich sind.

Daß ich noch lange nicht im 'grünen Bereich' bin, ist mir selbst klar.
Ich habe aber zumindest mal die Anzahl der Codezeilen, um einiges reduzieren können und hoffe daß der Code auch ein wenig besser ist.

Nachtrag:
Diese Funktion

Code: Alles auswählen

def fileclean(filename):
    try:
        os.remove(filename)
    except OSError:
        pass
habe ich durch diese Zeile

Code: Alles auswählen

            if os.path.exists(os.path.join(os.getcwd(), chance)):
                os.remove(chance)
in der Funktion 'handle_menu' ersetzt.
BlackJack

@Nobuddy: Das ein Pfad existiert heisst nicht, dass man ihn problemlos löschen kann.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

@BlackJack: Ich überprüfe ja, ob die Datei 'chance' in diesem Pfad existiert. Ich lösche ja nicht den Pfad sondern die Datei, wenn vorhanden und ich die notwendigen Zugriffsrechte auf diesen Pfad und Datei habe.

Die Funktion spellings habe ich entfernt und in der Funktion 'search_entry' die Suchzeile geändert.
Die Auswahlmöglichkeit bei SPELLING, habe ich so geändert (DOC-String, privatparam.py)

Code: Alles auswählen

:param:SPELLING
0 = Groß-/Kleinschreibung wird beachtet!
1 = groß-/kleinschreibung wird nicht beachtet, alle eingaben erfolgen
    in kleinschreibung!
Jetzt wird nicht mehr die Sucheingabe beeinflusst, sondern direkt in der Suche selbst.

Code: Alles auswählen

def search_entry(menupoint, result):
    entry = input('\nSuchbegriff: ')
    if entry:
        print("\nListe wird nach Suchbegriff durchsucht!")
        if not type(result) == list:
            with open(DATAPOOL, 'r') as infile:
                reader = csv.reader(infile, delimiter="\t", quotechar="^")
                contents = [row for row in reader if any(entry in
                        {0: x, 1: x.lower()}[SPELLING] for x in row)]
        elif type(result) == list:
            contents = [row for row in result if any(entry in
                    {0: x, 1: x.lower()}[SPELLING] for x in row)]
Ich hoffe, daß der Code so einigermaßen für Dich akzeptabel ist.

Ach ja, jetzt habe ich schon 100 Zeilen weniger Code, statt 318 nur noch 218. :D :wink:
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die Idee mit dem Wahrheitswert bei `SPELLING` ist gut. Du kannst es dir aber noch einfacher machen und an der Stelle anstatt eines Wörterbuchs einfach `(x, x.lower())[SPELLING]` verwenden. Denn du brauchst ja grundsätzlich nur ein zwei-elementriges Container-Objekt (Liste, Tupel, Menge, ...) und musst dafür also nicht extra passende Schlüssel anlegen, sondern arbeitest dann einfach auf dem Indexwert.

Den Namen `SPELLING` solltest du übrigens - wie auch viele deiner anderen Bezeichnungen - nochmal überdenken. Was du wahrscheinlich meinst, nennt sich "case-sensitive".

Zudem würde ich von einem Wert "0" erwarten, dass er für "Falsch" steht und von einer "1", dass sie für "Wahr" steht. Bei dir ist es aber umgekehrt.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu,
Dein Vorschlag vereinfacht das nochmal. :wink:
Der Wert 0 oder 1 für SPELLING, ist ja nur ein Zahlenwert der SPELLING zugewiesen wird.

Noch was zu Deiner Funktion 'get_formatted_table'.
Da bin ich am Überlegen, wie ich das hin bekommen kann, daß wenn die Zeilenausgabe größer als z.B. 20 Zeilen ist, 'formatted_lines' wieder eingefügt wird, um so immer einen Überblick zu haben, welchen Spaltenwert man sich gerade anschaut.
Einen kleinen Ansatz, habe ich evtl. hier, bin aber noch nicht sicher:

Code: Alles auswählen

    dataline = dict()
    headcount = HEADDISTANCE
    for numeration, text_items in enumerate(contents, 1):
        if len(contents) > headcount:
            headcount = numeration + HEADDISTANCE
            print(len(contents))
        dataline[numeration] = [text_items]
        numeration_string = '{}: '.format(numeration)
        formatted_lines.append(line_template.format(numeration_string,
                                                    *text_items))
        formatted_lines.append(blank_line)
    formatted_lines.append(dashed_line)
    print('\n'.join(formatted_lines))
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du brauchst auch hier keinen eigenen Counter mitzuführen. Ein Zeilenzähler wird doch bereits durch `enumerate()` verwaltet / zur Verfügung gestellt.

Meine Herangehensweise wäre hier wohl die Verwendung des Modulo-Operators. Zur Veranschaulichung:

Code: Alles auswählen

>>> max_lines_per_segment = 20
>>> 18 % max_lines_per_segment
18
>>> 19 % max_lines_per_segment
19
>>> 20 % max_lines_per_segment
0
>>> 21 % max_lines_per_segment
1
>>> 39 % max_lines_per_segment
19
>>> 40 % max_lines_per_segment
0
Das heißt also, immer wenn `numeration % max_lines_per_segment == 0` auftritt, dann weißt du, dass du die Kopfzeile einfügen musst.

Die Kopfzeile mitsamt der gestrichelten Linien kannst du ja einmalig erzeugen und dann jeweils einfügen. Ruhig weiterhin als Ansammlung von Zeilen:

Code: Alles auswählen

head_items = [dashed_line, headline, blank_line, dashed_line]
[...]
for line_number, text_items in enumerate(contents, 1):
    needs_headings = (line_number % max_lines_per_segment == 0)
    if needs_headings:
        formatted_lines.extend(head_items)
Wie du siehst, sind die Benennungen für Variablen auch bei mir nicht in Stein gemeißelt... :P
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Ich war fast schon wieder dabei, das umständlicher zu machen ... :lol:

Wenn ich das richtig umgesetzt habe, sieht jetz der Abschnitt bei mir so aus:

Code: Alles auswählen

    head_items = [dashed_line, headline, blank_line, dashed_line]
    formatted_lines = [dashed_line, headline, blank_line, dashed_line]
    dataline = dict()
    for line_number, text_items in enumerate(contents, 1):
        needs_headings = (line_number % MAX_LINES_PER_SEGMENT == 0)
        dataline[line_number] = [text_items]
        if needs_headings:
            formatted_lines.extend(head_items)
        numeration_string = '{}: '.format(line_number)
        formatted_lines.append(line_template.format(numeration_string,
                                                    *text_items))
        formatted_lines.append(blank_line)
    formatted_lines.append(dashed_line)
    print('\n'.join(formatted_lines))
Funktionieren tut er jedenfalls :wink:

Wenn ich so überlege, funktioniert jetzt alles so, wie ich es mir vorgestellt habe. :D
Das mit dem Zeilenumbruch innerhalb der Spalte, dürfte wohl nicht ganz so einfach werden.

Letztendlich würde nur noch eine GUI fehlen, in der das Ganze abläuft und die Einstellungen direkt gemacht werden können.
Aber das ist noch Zukunftsmusik, das wird noch ein sehr weiter Weg geben.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Den oberen Abschnitt würde ich anders schreiben:

Code: Alles auswählen

head_items = [dashed_line, headline, blank_line, dashed_line]
formatted_lines = head_items[:]
Dies sorgt dafür, dass eine Kopie von `head_items` für `formatted_lines` angelegt wird.

Wichtig dabei ist, dass du auch wirklich eine *Kopie* anlegst. Ein einfaches `formatted_lines = head_items` würde dazu führen, dass hinter beiden Namen dasselbe Objekt steckt. Veränderungen an einem würden damit auch zu Veränderungen am anderen führen, weil es nämlich dann gar kein "ein" und "anderes" gibt - es gäbe nur zwei verschiedene Wege *ein und dasselbe* Objekt zu erreichen (hoffe, du kannst mir folgen). Jedenfalls sparst du dir damit das Copy+Paste von Quelltext, vor allem in Hinblick darauf, dass sich das Design der Kopfzeilen vielleicht irgendwann mal ändert und du dann penibel darauf achten müsstest, dass es an beiden Stellen angepasst wird - was leicht vergessen werden kann. Durch ein "echtes" Kopieren des Listenobjektes umgehst du diese Gefahr.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Das hatte ich auch schon überlegt und versucht

Code: Alles auswählen

formatted_lines = head_items
was aber nicht funktionierte.
Danke für den Tip! :wink:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Eine weitere Änderung bei 'menu.py', ist das Arbeiten mit einer Kopie der Originaldatei, was wohl in Hyperionś ursprünglichem Code, mit der Menüoption 'save' wahrscheinlich auch so angedacht war.

Die Vorgehensweise ist die, daß bei Änderung (Daten verändern, Neue Daten hinzu, Daten löschen), eine Kopie der Originaldatei erstellt wird und mit dieser weitergearbeitet wird. Alle Änderungen sind zuerst nur in der Kopie vorhanden und erst durch den Menüpunkt 'Änderungen übernehmen', wird die Originaldatei aktualisiert und die Kopie wird anschließend gelöscht.

Jetzt sind zwar wieder ein paar Zeilchen mehr dazu gekommen, aber damit lässt es sich schon richtig gut arbeiten. :D :wink:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
habe jetzt eine Version, die Ihr Euch anschauen könnt: https://gist.github.com/3621627

Ich hoffe mal, daß Ihr den ersten Fehler nicht gleich nach 5 Sekunden findet. :lol:
Nein ehrlich, habe mir wirklich Mühe gegeben, Euren Anforderungen gerecht zu werden. :wink:

Den Code habe ich versucht in der maximalen Zeichenbreite unterzubringen.
Wenn also manches dadurch nicht so schön aussieht ... könnt Ihr mir ja Tipps geben.
Auch habe ich versucht LC im Code zu verwenden, wo es mir möglich war.

Bestimmt lässt sich das noch weiter verbessern und hoffe, daß Ihr mir dazu Tipps gebt! :wink:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Sodele, da ich nach 5 Sekunden nichts von Euch gehört habe, komme ich zum Ende dieses Projektes. :D

In menu.py selbst gab es noch kleinere Korrekturen.
Was ich komplett neu gemacht habe, ist ein Modul für das Erstellen und Verwalten der Einstellungen, was mir recht gut geglückt ist, :D
Ich habe dafür den Namen settingmenu.py vergeben und die Datei in der die Einstellungen gespeichert werden, heißt nicht mehr privatparam.py sondern settings.py.

Für Diejenigen, dies es noch interessiert, hier die fertige Version: https://gist.github.com/3667523

Danke, an Alle, die mir geholfen haben! :wink:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu,
Dein letzter Post, ist ja eine kleine Weile her, möchte aber wenn möglich, nochmals daran anknüpfen.
snafu hat geschrieben:Hab jetzt einfach mal die komplette Formatierungs-Funktionalität geschrieben. Aufgrund der speziellen Anforderungen, die du an die Ausgabe stellst, sind es doch ein paar Zeilen mehr geworden. Manches kann man vermutlich noch besser machen, aber ich hoffe trotzdem, den Quelltext etwas verständlicher für die Allgemeinheit hinbekommen zu haben: https://gist.github.com/3527463

Wie du siehst, habe ich den Info-Text bei den Testdatensätzen stark gekürzt, damit das schon von dir genannte Problem der Überlänge nicht auftritt. Wobei man auch hier sein Terminal-Fenster im Vollbildmodus laufen lassen sollte.

Eine Implementierung mit passendem Zeilenumbruch kommt dann in der nächsten Maus (irgendwann). ;)
Deine Funktion, funktioniert wirklich prima. Einziger Wermutstropfen, sind überlange Zeilen, die dann das Layout sprengen.

Habe Deine Funktion als Modul laufen, damit ich von verschiedenen Modulen aus, darauf zugreifen kann.

Habe den Code für die neue Anforderung etwas erweitert, bin allerdings noch am Rätseln, wie ich das umsetzen kann, bei überlangen Zeilen, einen Zeilenumbruch innerhalb der jeweiligen Spalte zu erzeugen.

Code: Alles auswählen

def window_size():
    # Fenstergröße des Terminals/Konsole ermitteln.
    # Ausgabe: Zeilen, Zeichen
    rows, columns = os.popen('stty size', 'r').read().split()
    return rows, columns


def get_formatted_table(menupoint, HEADINGS, contents, spacing=4,
                                                numeration_width=10):
    rows, columns = window_size()
    lines = [HEADINGS] + contents
    column_widths = [max(len(item) for item in column_items)
                     for column_items in zip(*lines)]
    total_width = sum(column_widths) + (len(column_widths) - 1) * spacing
    total_width += numeration_width
    if total_width > int(columns):
        print('Layoutfehler, die Ausgabezeile ist um %s Zeichen größer, \
            \nals die Breite des Terminals/Konsole!' %
                                        (total_width - int(columns)))
    dashed_line = total_width * '-'
    blank_line = total_width * ' '
    numeration_spec = '{{:>{}}}'.format(numeration_width)
    column_specs = ['{{:{}}}'.format(width) for width in column_widths]
    line_template = numeration_spec + (spacing * ' ').join(column_specs)
    headline = line_template.format('Nummer: ', *HEADINGS)
    head_items = [dashed_line, headline, blank_line, dashed_line]
    formatted_lines = head_items[:]
    dataline = dict()
    for line_number, text_items in enumerate(contents, 1):
        needs_headings = (line_number % MAX_LINES_PER_SEGMENT == 0)
        dataline[line_number] = [text_items]
        if needs_headings:
            formatted_lines.extend(head_items)
        numeration_string = '{}: '.format(line_number)
        formatted_lines.append(line_template.format(numeration_string,
                                                    *text_items))
        formatted_lines.append(blank_line)
    formatted_lines.append(dashed_line)
    print('\n'.join(formatted_lines))
    return menupoint, dataline
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Naja, man könnte entweder überlegen, ob man das vorhandene `line_template` benutzt und bei Überlänge in einer neuen Zeile die dementsprechend nicht zu benutzenden Spalten einfach mit leeren Strings füllt und in die Spalte mit Überlänge halt den Rest des Textes packt (in einer Schleife, damit auch mehr als nur *eine* zusätzliche Zeile genutzt werden können) oder aber man geht davon aus, dass eh immer nur die letzte Spalte befüllt werden muss und berechnet daher einfach vorab den nötigen Leerraum.

Ich würde wahrscheinlich die mir persönlich schon wieder viel zu aufgeblähte Funktion in mehrere einzelne Funktionen unterteilen und etwas einigermaßen abstraktes (und damit auch wiederverwendbares) erstellen. Eventuell sogar als Klasse. Das ist mir aber momentan etwas zu aufwändig, zumal ich zur Zeit andere wichtigere Dinge im Kopf bzw auf meiner TODO-Liste habe. Kann sein, dass ich zum Wochenende was dazu baue - soll aber kein Versprechen sein.
Antworten