python2to3

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

Das mit den Abständen kann man schöner machen:

Code: Alles auswählen

>>> row = ['spam', 'ham', 'egg']
>>> print (3 * ' ').join(row)
spam   ham   egg
Wenn du bestimmte Spaltenbreiten willst:

Code: Alles auswählen

>>> '{:20}'.format('foo')
'foo                 '
>>> '{:20}'.format(3 * 'foo')
'foofoofoo           '
Wie du siehst, ist die Ausgabe hier immer mindestens 20 Zeichen lang.

Das Ganze lässt sich noch dynamisch bauen, also dass die nötige Spaltenbreite automatisch aus dem längsten Element eine Reihe ermittelt wird. Das ist allerdings etwas komplexer und führt ein paar Dinge ein, die du glaube ich noch nicht kennst. Vielleicht fängst du also erstmal klein an und baust den Code so um, dass er das String-Formatting für feste Breitenangaben nutzt.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu,
das ist mal ein guter Ansatz, mit dem ich was anfangen kann, Danke! :wink:
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Da ich in deinem Code gesehen hatte, dass du bereits das Spaltenmaximum ermittelst, hier dann doch mal der Weg, den ich einschlagen würde:

Code: Alles auswählen

>>> rows
[['foo', 'longfoo', 'longestfoo'], ['a lot of spam', 'nomalspam', 'spam']]
>>> [max(len(item) for item in column_items) for column_items in zip(*rows)]
[13, 9, 10]
`zip()` vereint sozusagen die `i`-ten Elemente von "Auflistungen" - genauer gesagt: von allem, über das iteriert werden kann. Mal zum besseren Nachvollziehen:

Code: Alles auswählen

>>> zip([1,2,3], [1,3,6])
[(1, 1), (2, 3), (3, 6)]
Aber genau so gut auch:

Code: Alles auswählen

>>> zip("abc", "acf")
[('a', 'a'), ('b', 'c'), ('c', 'f')]
`zip()` arbeitet allerdings etwas anders als `.join()`. Während `.join()` *ein* Objekt erwartet, über welches es iteriert, erwartet `zip()` eine *beliebige* Anzahl von Argumenten, d.h. sozusagen: `[rows[0], rows[1], rows[2], ...]`, nur eben als `zip(rows[0], rows[1], rows[2], ...)`. Um dies zu bewerkstelligen, wird der Stern benötigt. Details zu dieser Technik sind übrigens in der Doku im Abschnitt Unpacking Argument Lists beschrieben.

Und im linken Teil des o.g. Ausdrucks - also alles, was von `max(...)` umschlossen wird - nehme ich mir jeweils *eine* Spalte aus dem Ergebnis von `zip(*rows)` und durchlaufe deren Elemente, um das jeweilige Maximum zu ermitteln. `column_items` beinhaltet also nicht eine Liste aller Spalten, sondern es bekommt bei jedem Durchlauf einen neuen Wert zugewiesen, der die Elemente der *aktuellen* Spalte repräsentiert. Das kann man anfangs IMHO leicht durcheinander bringen.

So, und zu guter Letzt soll die Spaltenlänge ja wie gesagt dynamisch in die Formatierungsangaben eingefügt werden. Dazu nutzt man verschachteltes String-Formatting - also ein Formatierungsausdruck, der seinerseits aus einem anderen Formatierungsausdruck ermittelt wird. Für das Beispiel aus dem vorherigen Beitrag ginge das so:

Code: Alles auswählen

>>> '{:{}}'.format('foo', 20)
'foo                 '
>>> '{:{}}'.format(3 * 'foo', 20)
'foofoofoo           '
Die Angabe hinter dem Doppelpunkt wird also aus einer "externen" Quelle eingefügt, quasi so als wenn sie ein Wort oder ähnliches wäre.

Will man hingegen nur die Formatierungsangabe ermitteln, aber noch keine Zeichenkette einsetzen, dann muss man die äußeren Klammern escapen, d.h. vor einer Interpretierung seitens der Formatting-Engine schützen:

Code: Alles auswählen

>>> '{:{}}'.format(20)  # falsch
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: tuple index out of range
>>> '{{:{}}}'.format(20)  # richtig
'{:20}'
Die zu schützenden Klammern (hier: das äußere Klammernpaar) werden also doppelt hingeschrieben.

Und jetzt möchtest du vielleicht mal probieren, wie du in einer Schleife deine eigenen Maximalwerte benutzen kannst. Als kleinen Hinweis möchte ich noch erwähnen, dass du zur Ermittlung der Maximalwerte in Spalten, aber beim späteren Einsetzen besser in Zeilen denken solltest. Ich würde Ermitteln und Einsetzen also voneinander trennen.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu,
Danke für Deinen ausführlichen Input, da steckt eine Menge Know-how drin!
Werde Deinen Beitrag in Ruhe durcharbeiten, bis ich alles verstanden habe.

Habe das Gefühl, daß wir jetzt auf dem richtigen Weg sind. :wink:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Ich habe jetzt mal den betreffenden Teil der Funktion 'selection', soweit abgeändert.
Evtl. habe ich bei der Umsetzung Fehler gemacht, die noch zu korrigieren sind, aber es sieht schon deutlich besser aus und mit der Spaltenumrechnerei ist auch kein Gefrickel mehr. :)

Hier mal den betreffenden Codeabschnitt:

Code: Alles auswählen

def selection(menupoint, result):
    if result:
        # Maximale Zeichenbreite von splitinfos
        skymax = [max(len(item) for item in column_items) for column_items in zip(*[splitinfos])]

        # Maximale Zeichenbreite von result
        resmax = ([max(len(item) for item in column_items) for column_items in zip(*result)])

        printtable = list()
        for index, row in enumerate(result):
            counter = 0
            linedata = list()
            while True:
                if counter == 0:
                    line = index + 1
                    linedata.append(line)
                else:
                    line = row[counter - 1]
                    linedata.append(line)
                if counter == len(row):
                    printtable.append(linedata)
                    break
                counter += 1

        if len(result) == 1:
            print("\nDie Suche ergab folgendes Ergebnis:")
        else:
            print("\nDie Suche ergab folgende Ergebnisse:")

        # Kopfzeile
        nr = '{:{}} :'.format(splitinfos[0], skymax[0])
        p2 = '{:{}}'.format(splitinfos[1], resmax[0] + 4)
        p3 = '{:{}}'.format(splitinfos[2], resmax[1] + 4)
        inf = '{:{}}'.format(splitinfos[3], resmax[2] + 4)
        line = '{}'.format('-' * (skymax[0]+ resmax[0] + resmax[1] + skymax[3] + len(splitinfos) * 10))
        print(line)
        print(nr, p2, p3, inf)
        print(line)

        dataline = dict()
        for row in printtable:
            number = '{:{}} :'.format(row[0], skymax[0])
            py2 = '{:{}}'.format(row[1], resmax[0] + 4)
            py3 = '{:{}}'.format(row[2], resmax[1] + 4)
            info = '{:{}}'.format(row[3], resmax[2] + 4)
            print(number, py2, py3, info)
            dataline[row[0]] = [row[1:]]
        print('-----')
Kommt es schon annähernd so hin, oder habe ich es falsch umgesetzt?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Nobuddy hat geschrieben:Kommt es schon annähernd so hin, oder habe ich es falsch umgesetzt?
Sieht schon ganz gut aus, aber du solltest den ganzen doppelten Code loswerden. Einige Zeilen wurde da ja offensichtlich nur kopiert und ganz leicht verändert. An den Namen solltest du auch noch arbeiten, die sagen teilweise noch nichts aus (p2, p3, inf?).

Vielleicht möchtest du aber auch einfac texttable verwenden?
Das Leben ist wie ein Tennisball.
BlackJack

Tolle ``while``-Schleife… m)
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das sieht immer noch arg gefrickelt aus. Poste mal die Ausgabe der Funktion. Ich bin mir recht sicher, dass sich mit weitaus weniger Codzeilen dasselbe Ergebnis erzeugen lässt...
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
mein letzter Post, war nur der Anfang.
Dank Eures Input, weiß ich schon, wo ich hin muß.
Gebt mir noch ein wenig Zeit, ich poste Euch dann hoffentlich eine bessere Alternative. :wink:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
bin wieder einen Schritt weiter, der Code reagiert nun flexibel auf die Anzahl der Spalten. :)

Ich poste hier mal den kompletten Code von python2to3.py, filestart.py und pythoninfo.txt.
https://gist.github.com/3481807
Der betreffende Code befindet sich in der Funktion 'selection'

Bestimmt lässt sich da noch vieles mehr vereinfachen und meistens wenn ich die Kiste ausgeschaltet habe, kommen neue und als noch bessere Ideen ... :D aber ich glaube, daß ich mich auf Euch verlassen kann und Ihr mir schon die Richtung zeigt ... :wink:
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Sodele, da hat sich noch einiges getan, in der Funktion 'selection'! :D

Poste hier, wie gehabt den aktuellen Stand: https://gist.github.com/3490157
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Das soll nicht dein Ernst sein, oder? Eine über 100 Zeilen lange Funktion in dutzende Aufgaben vermischt und kopiert sind. Lese einfach den ganzen Thread noch einmal und wende es auf deine neue Funktion an.
Das Leben ist wie ein Tennisball.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

snafu hat geschrieben:

Code: Alles auswählen

>>> '{:20}'.format('foo')
'foo                 '
>>> '{:20}'.format(3 * 'foo')
'foofoofoo           '
Das geht auch auf die altmodische Art:

Code: Alles auswählen

>>> "foo".ljust(20)
'foo                 '
Siehe: http://docs.python.org/library/string.html#string.ljust

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo jens,
beim Googeln habe ich auch ljust, rjust. Hatte es aber durch Deinen ersten Vorschlag mit format, nicht weiter verfolgt.
Habe es jetzt im Code geändert, ist von der Anwendung einfacher zu handhaben.

Code: Alles auswählen

    if result:
        # Maximale Zeichenbreite von result
        resmax = ([max(len(item) for item in column_items) for column_items in zip(*result)])

        # Kopfzeile
        # Maximale Zeichenbreite von headinfo
        headmax = [max(len(item) for item in column_items) for column_items in zip(*[headinfo])]

        # Maximale Zeichenbreite von resmax mit headmax vergleichen
        counter = 0
        rowmax = dict()
        while True:
            if headmax[counter] and counter == 0:
                rowmax[counter] = headmax[counter] + 4
            elif headmax[counter] and counter > 0:
                rowmax[counter] = max(headmax[counter], resmax[counter - 1]) + 4
            if counter == len(headmax) - 1:
                break
            counter += 1

        counter = 0
        separate = 0
        headline = list()
        rows = len(headinfo)
        while True:
            if counter == 0:
                headline.append((headinfo[0] + ': ').rjust(rowmax.get(counter)))
                separate += rowmax.get(counter)
            else:
                headline.append(headinfo[counter].ljust(rowmax.get(counter)))
                separate += rowmax.get(counter)
            counter += 1
            if counter == rows:
                break
        separateline = '{}'.format('-' * (separate))
        headprint = '{}\n{}\n{}'.format(separateline, ''.join(headline), separateline)

        # Index erstellen und zeilenweise einlesen der einzelnen Spalten
        # Zuweisen der maximalen Zeichenbreite für Printausgabe.
        # Einheitliches, spaltenbezogenes Printlayout
        counter = 0
        dataline = dict()
        printline = list()
        for index, row in enumerate(result):
            data = list()
            printdata = list()
            data.append(index + 1)
            printdata.append((str(index + 1) + ': ').rjust(rowmax.get(counter)))
            printdata.append(row[counter].ljust(rowmax.get(counter + 1)))
            while True:
                counter += 1
                data.append(row[counter])
                if rowmax.get(counter + 1):
                    printdata.append(row[counter].ljust(rowmax.get(counter + 1)))
                if counter == len(row) - 1:
                    dataline[data[0]] = [data[1:]]
                    printline.append(printdata)
                    counter = 0
                    break

        if len(result) == 1:
            print("\nDie Suche ergab folgendes Ergebnis:")
        else:
            print("\nDie Suche ergab folgende Ergebnisse:")

        # Ausgabe Konsole/Terminal
        print(headprint)
        for row in printline:
            print(''.join(row))
        print(separateline)
@EyDu, es ist richtig, je kürzer, um so besser.
Aber manchmal erfordert es einfach ein bisschen mehr, vielleicht schaust Du Dir einfach nochmals genauer den Ablauf der Funktion selection an.
Ich will nicht behaupten, daß es da nicht noch kürzer geht, aber alles zu seiner Zeit.
Jetzt war mir zuerst wichtig, einen unabhängigen, flexiblen Code zu bekommen.
Dies habe ich jetzt mit meinen Möglichkeiten erreicht. Egal wie viel Spalten die Datenquelle hat, wird diese verarbeitet. Wichtig ist die Definition der Spalten, die ich in privatparam.py ausgelagert habe.
Und auch Kopieren, muß nicht unbedingt falsch sein. :wink:

Ich freue mich immer wenn Kritik und damit verbundene Verbesserungsvorschläge kommen, wie z.B. von jens.
Was nicht heißen soll, komplett fertigen Code von Euch zu erhalten!

Also, es wäre hilfreich und produktiv, sachliche Kritik mit Bezug und Anregungen, von Euch zu erhalten! :wink:
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du solltest dich mal näher mit `.join()` befassen. Diese Methode arbeitet nämlich nicht nur auf leeren Zeichenketten, sondern nimmt im Grunde eine beliebige Zeichenkette als "Verbindungsglied". So kann man zum Beispiel den Spaltenabstand durchaus bei der Breitenangabe für das String-Formatting weglassen. Den s etzt du dann einfach weiter unten (Zeile 97) mittels `(4 * ' ').join(headline)` ein. Und wenn man schon dabei ist, kann man in selbiger Zeile auch ein `.join()` auf die Newlines machen. Sähe dann insgesamt so aus:

Code: Alles auswählen

headprint = '\n'.join([separateline, (4 * ' ').join(headline), separateline])
Die Zeile darüber lässt sich auch kürzer schreiben:

Code: Alles auswählen

sepline = separate * '-'
Das String-Formatting kann man an der Stelle also ganz einfach weglassen. Persönlich bevorzuge die Zahlenangabe als ersten Faktor und den zu duplizierenden String dahinter. An `sepline` scheiden sich vielleicht die Geister, weil es eine Abkürzung ist. `sep` ist jedoch ein in Python weit verbreiteter Ausdruck und daher würde ich hier die Faulheit einfach siegen lassen. `separate` als Bezeichner für die Anzahl an Trennzeichen ist übrigens auch kein wirklich guter Name. Vielleicht fällt dir da noch was besseres ein.

Eine Einsatzmöglichkeit für dein neu erlerntes "join-Wissen" wäre übrigens auch Zeile 145:

Code: Alles auswählen

if os.path.exists('{}/{}'.format(os.getcwd(), chance)):
wird zu:

Code: Alles auswählen

if os.path.exists('/'.join([os.getcwd(), chance])):
oder noch besser und fast immer zu bevorzugen:

Code: Alles auswählen

if os.path.exists(os.path.join(os.getcwd(), chance)):
Beachte, dass `os.path.join()` wieder mit einer beliebigen Anzahl an Argumenten arbeitet, ähnlich wie `.format()`.

So, und das war jetzt nur ein ganz kleiner Ausschnitt von dem, was du falsch gemacht hast. Oft frage ich mich bei solchen Threads ja, ob der Lerngehalt wirklich so riesig ist, wenn einem quasi alles vorgesagt wird und man es mehr oder weniger blind übernimmt. Gut, du hast sicherlich ein paar eigene Kreationen dabei, nur sind die leider durch die Bank von falschen Ansätzen geprägt. Ich würde ehrlich gesagt erwarten, dass man von sich aus auch mal in die Doku schaut, dann würdest du z.B. recht schnell die Fähigkeiten von `.join()` erkennen und man müsste dir nicht alles unter die Nase halten. Naja, offtopic...
BlackJack

@Nobuddy: Sachliche Kritik: OMG ist das wieder ein gruseliger Code, bei dem man gar nicht weiss wo man anfangen soll. Anregung: Lass das mit dem Programmieren lieber sein. SCNR
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Das war sachliche Kritik. Warum noch einmal alles hinschreiben, wenn die Hinweise eh schon im ganzen Thread verteilt sind?
Das Leben ist wie ein Tennisball.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Unter sachlicher Kritik verstehe ich, z.B. Zeile 102 bis 120 geht einfacher, schau Dir mal die xyz-Methode an, evtl. ein kleines Beispiel dazu (wie es mach Andere hier machen).

Ich sehe auch keinen Grund, das Programmieren sein zu lassen.
Es gibt für jede Umsetzung, bestimmt mehrere Möglichkeiten, so daß unter Euch 'Profiś' der Code auch nicht immer gleich ist.
Euer Vorteil ist wohl (trifft bestimmt nicht auf alle zu), eine gezielte schulische Laufbahn absolviert zu haben, welches Euch heute ermöglicht, die Dinge so zu sehen und zu verstehen. Es gibt aber auch Ausnahmen, die das Glück nicht hatten (bitte kein Mitleid). Aber haben die nicht auch das Recht, sich Ihre Ziele zu stecken und zu verwirklichen?
Keine Angst, ich werde nie mich beruflich zum Programmiere orientieren, dafür bin ich einfach schon zu alt und habe ein gesundheitliches Handicap. :wink:
deets

Das Problem mit sachlicher Kritik an deinem Code ist: sie verhallt nahezu ungehoert, bestenfalls verbessert sie eine Kleinigkeit fuer einen kurzen Moment, aber sofort verfaellst du wieder in alte Muster.

Und *das* ist das Problem. Es ist ok, von niedrigem Niveau zu starten. Und auch die Geschwindigkeit, in der man Fortschritte macht mag mal mehr, mal weniger hoch sein. Aber langsam kristallisiert sich eben heraus, dass es nichts fruchtet, deinen Code wieder und wieder mit denselben Problemen zu betrachten und dazu etwas auszufuehren.

Anders gesagt: du hast das Recht, zu programmieren was & wie du willst. Und wir haben das Recht, nichts dazu zu sagen :)
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nochmal was Konkretes zum Code: Guck dir unbedingt mal `range()` bzw `xrange()` (unter Python 2.x) an. Das erspart dir diese umständlichen "while-True-counter-+=1" Schleifen. Du gibst `range()` einfach einen Zielwert mit. `range()` fängt dann bei 0 an, zu zählen und hört auf, wenn es den Zielwert sieht. Dieser letzte Wert kommt nicht mehr in die Zählung rein. D.h. an Stellen, wo du ein `if counter == len(xyz) - 1: break` machst, kannst du es ersetzen durch ein `for index in range(len(xyz)):`. Wenn du bevorzugt `counter` statt `index` oder `i` verwendest, dann mach es halt so.

Im Übrigen ist selbst dies in Python eigentlich ein Anti-Pattern. Man iteriert bevorzugt über die Objekte an sich, anstatt eine Zählervariable zu benutzen - also: `for obj in xyz:`. Am Besten organisierst du deinen Code so um, dass du möglichst auf jegliche Counter verzichten kannst. Ausgenommen davon ist das Mitzählen von Zeilennummern via `enumerate()` (und wahrscheinlich noch ein paar andere Anwendungsfälle). Manchmal geht es halt nicht anders.

Und bitte überlege dir doch mal, ob du für wiederkehrende Aufgaben nicht doch lieber Hilfsfunktionen einführen möchte. Das würde den Quelltext wesentlich aufgeräumter erscheinen lassen und es macht damit meist mehr Spass, den Code zu lesen. Treffendere Funktionsbenennungen wären übrigens auch schön. Eine Funktion sollte idealerweise genau *einen* Handgriff (also eine Teilaufgabe) erledigen. Das fehlt mir bei dir noch viel zu sehr.

Und ohne dir jetzt groß was erzählen zu wollen, aber du musst nicht (übertrieben gesagt) dreimal am Tag eine neue Version deines Code abliefern. Man kann auch ruhig mal für längere Zeit daran herumschrauben und es *dann* präsentieren. Außer natürlich, du hast tatsächlich den Eindruck, dass jede von dir abgelieferte Version in etwa den Stand deiner aktuellen Fähigkeiten wiederspiegelt.

Ich persönlich mag es auch nicht so sehr, wenn die hier mitschreibenden Anfänger einen (bzw das Forum) zu sehr als eine Art Mentor ansehen. Nachfragen auf einem völlig neuem Gebiet sind ja nicht das Problem, aber etwas Eigeninitiative (vor allem in Form von eigener Recherche) sollte schon drin sein. Ich will nicht sagen, dass ich nicht auch mal klein angefangen hab und auch nicht den Eindruck vermitteln, dass Fragen von Anfängern grundsätzlich zu dumm sind, als dass sie hier gestellt werden dürften (auf dass sich am Ende keiner mehr traut, den Mund aufzumachen), aber ab einem gewissen Punkt, geht man - guter Wille hin oder her - den Leuten zunehmend auf die Nerven.

Eine gute Herangehensweise wäre es wohl, wenn du uns nicht einfach Code zum Fraß vorwirfst, sondern konretere Fragen zu bestimmten Code-Stücken stellst. Aber ich gebe zu, das ist manchmal sehr leicht dahergeredet. Gewissermaßen muss bei dir erst noch eine Art Knoten platzen, aber ich könnte diesen Knoten nicht mal präzise beschreiben. Oder wie man ihn zum Platzen bringt. Ich weiß nur, dass sehr viele Leute diesen Knoten irgendwann nicht mehr haben. Und diejenigen, die ihn nicht loswerden können, hören vermutlich irgendwann mit dem Programmieren auf...
Antworten