Ausgabe in Spalten wie beim Befehl "ls"
Ich will die Elemente eines iterierbaren Objekts spaltenweise ausgeben. So eine Art Mischung aus [mod]pprint[/mod] und dem Unix-Tool `ls` also.
Im Prinzip klappt das schon ganz gut. Ich ermittle das längste Element und wende anschließend `str.lstrip()` an, um den gleichen Abstand für alle Elemente zu bekommen.
Jetzt würde ich aber gerne, so wie bei `ls`, Spalten von unterschiedlicher Breite haben, d.h. wenn relativ kurze Zeichenketten in einer Spalte landen, dann reichen natürlich entsprechend kleinere Werte für die Breite dieser Spalte aus.
Mein Problem ist, dass ich vorher nicht weiß, wieviele Spalten am Ende bei der Ausgabe herauskommen, so dass ich leider auch keinem Element seine Spalte zuordnen könnte. Denn 1-2 schmale Spalten können ja durchaus dazu führen, dass eine Spalte zusätzlich entstehen kann und dementsprechend alle Elemente umverteilt werden müssen.
Bei meiner jetzigen Version mit fester Spaltenbreite sage ich einfach `Max. an Zeichen für eine Zeile / (Zeichen des längsten Elements + gewünschter Mindestabstand)`.
Damit habe ich eine feste Anzahl an Elementen, die ich gruppenweise herausnehme und später mit einem Zeilenumbruch von den anderen Gruppen getrennt ausgebe. Die Schleife bricht ab, sobald keine neue Zeile mehr zustande kommt.
Hat jemand einen Ansatzpunkt für mich, wie man das am Besten für Spalten in variabler Anzahl und mit variabler Breite umsetzen kann? Zur Veranschaulichung hier mein Code: http://paste.pocoo.org/show/152070/
Im Prinzip klappt das schon ganz gut. Ich ermittle das längste Element und wende anschließend `str.lstrip()` an, um den gleichen Abstand für alle Elemente zu bekommen.
Jetzt würde ich aber gerne, so wie bei `ls`, Spalten von unterschiedlicher Breite haben, d.h. wenn relativ kurze Zeichenketten in einer Spalte landen, dann reichen natürlich entsprechend kleinere Werte für die Breite dieser Spalte aus.
Mein Problem ist, dass ich vorher nicht weiß, wieviele Spalten am Ende bei der Ausgabe herauskommen, so dass ich leider auch keinem Element seine Spalte zuordnen könnte. Denn 1-2 schmale Spalten können ja durchaus dazu führen, dass eine Spalte zusätzlich entstehen kann und dementsprechend alle Elemente umverteilt werden müssen.
Bei meiner jetzigen Version mit fester Spaltenbreite sage ich einfach `Max. an Zeichen für eine Zeile / (Zeichen des längsten Elements + gewünschter Mindestabstand)`.
Damit habe ich eine feste Anzahl an Elementen, die ich gruppenweise herausnehme und später mit einem Zeilenumbruch von den anderen Gruppen getrennt ausgebe. Die Schleife bricht ab, sobald keine neue Zeile mehr zustande kommt.
Hat jemand einen Ansatzpunkt für mich, wie man das am Besten für Spalten in variabler Anzahl und mit variabler Breite umsetzen kann? Zur Veranschaulichung hier mein Code: http://paste.pocoo.org/show/152070/
Ich würde es so machen: Messe jeweils die Breiten für 1-spaltig, 2-spaltig, ... bis n-spaltig. Nimm die Spaltenzahl, die gerade noch in die gewählte Breite passt. http://paste.pocoo.org/show/152205/ zeigt es, doch ich schäme mich für die nichtssagenden Variablennamen.
Stefan
Stefan
Ganz perfekt ist die Funktion noch nicht. Wenn ich sie in IPython mit `os.environ` bei einer Breite von 157 (entspricht bei mir einem maximierten Terminal-Fenster) teste, dann füllt er einige Spalten nicht bis ganz unten aus:
Code: Alles auswählen
COLORTERM GDM_KEYBOARD_LAYOUT GTK_MODULES LESSOPEN PATH SHLVL TERM XAUTHORITY
DBUS_SESSION_BUS_ADDRESS GDM_LANG GTK_RC_FILES LOGNAME PWD SPEECHD_PORT USER XDG_DATA_DIRS
DESKTOP_SESSION GNOME_DESKTOP_SESSION_ID HOME LS_COLORS SESSION_MANAGER SSH_AGENT_PID USERNAME XDG_SESSION_COOKIE
DISPLAY GNOME_KEYRING_PID LANG ORBIT_SOCKETDIR SHELL SSH_AUTH_SOCK WINDOWID _
GDMSESSION GNOME_KEYRING_SOCKET LESSCLOSE
Aufbauend auf sma und snafu habe ich mich auch einmal versucht:MfG
HWK
Edit: Wenn man die Ausgabe zeilen- statt spaltenweise haben möchte, braucht man nur temp1 zu ändern:
Code: Alles auswählen
def pprint_elems(iterable, indent=2, line_len=40):
list_len = len(iterable)
for cols in xrange(1, len(lst) + 1):
lines = list_len / cols
if list_len % cols != 0:
lines += 1
# Letzte Spalte leer?
if lines * cols >= list_len + lines:
continue
temp1 = [iterable[col * lines:col * lines + lines]
for col in xrange(cols)]
temp2 = [max(len(item) for item in col) for col in temp1]
if sum(temp2) + (cols - 1) * indent > line_len and cols > 1:
break
col_list = temp1
width_list = temp2
line_list = []
for i in xrange(len(col_list[0])):
line = []
for col, width in zip(col_list, width_list):
try:
line.append(col[i].ljust(width + indent))
except IndexError:
break
line_list.append(''.join(line).rstrip())
return '\n'.join(line_list)
import random
lst = ['x' * random.randrange(1, 12) for i in range(16)]
print lst
print pprint_elems(lst)
HWK
Edit: Wenn man die Ausgabe zeilen- statt spaltenweise haben möchte, braucht man nur temp1 zu ändern:
Code: Alles auswählen
temp1 = [iterable[col:list_len + 1:cols]
for col in xrange(cols)]
Besten Dank @ HWK. Die Funktion ist super. Die Namen muss ich noch etwas überarbeiten. Zudem sind zwei Fehler drin. In Zeile 3 brauchst du die Länge von `iterable`, nicht von `lst`. Das klappt hier nur, weil `lst` auf globaler Ebene definiert wurde. Zudem gibt es Verschiebungen bei Unicode-Zeichen (z.B. Umlaute), da er dort für die Repräsentation eine Länge von 2 pro Zeichen erhält und `print` dann halt nur 1 Zeichen anzeigt. Dies lässt sich beheben, wenn man am Anfang etwas wie `iterable = [str(x).decode('utf-8') for x in iterable]` einfügt. Sonst halt noch wie gesagt die Namensgebung und ein paar kleinere Optimierungen, aber ich denke insgesamt kann ich den Code sehr gut weiterverwenden. 

@jerch, deine Version finde ich auf dem ersten Blick schon etwas leserlicher/effizienter, aber sie beachtet anscheinend nicht die Reihenfolge der Elemente. Zumindest nicht konsequent. Wende es z.B. mal auf ein `sorted(os.listdir('.'))` an. Bei mir steht dann `Öffentlich` ganz zum Schluss und auch vorher habe ich ein paar Sprünge drin.
Zum Auslesen der Terminalgröße: Ich weiß, dass einige Snippets im Netz das so machen, aber ich persönlich hätte lieber eine *Ausnahme*, wenn die Größe nicht ermittelt werden kann. Wie man damit umgeht, kann man dann selbst entscheiden.
Auch das plattformabhängige Importieren hätte ich lieber an den Anfang zu den anderen Imports gesetzt, zwecks Übersicht. Das mag aber alles Geschmackssache sein und lässt sich ja leicht anpassen.
Zum Auslesen der Terminalgröße: Ich weiß, dass einige Snippets im Netz das so machen, aber ich persönlich hätte lieber eine *Ausnahme*, wenn die Größe nicht ermittelt werden kann. Wie man damit umgeht, kann man dann selbst entscheiden.
Auch das plattformabhängige Importieren hätte ich lieber an den Anfang zu den anderen Imports gesetzt, zwecks Übersicht. Das mag aber alles Geschmackssache sein und lässt sich ja leicht anpassen.

Hm, ich habs nicht ausgiebig getestet, kann sein dass mir da irgendwie ein slice-offset unterlaufen ist. Mitsnafu hat geschrieben:@jerch, deine Version finde ich auf dem ersten Blick schon etwas leserlicher/effizienter, aber sie beachtet anscheinend nicht die Reihenfolge der Elemente. Zumindest nicht konsequent. Wende es z.B. mal auf ein `sorted(os.listdir('.'))` an. Bei mir steht dann `Öffentlich` ganz zum Schluss und auch vorher habe ich ein paar Sprünge drin.
Code: Alles auswählen
[str(i)+'a' * random.randrange(5,15) for i in xrange(29)]
Code: Alles auswählen
[str(i)+j for i,j in enumerate(sorted(os.listdir('.')))]
Beachte bitte auch, dass sorted wie folgt sortiert:
Code: Alles auswählen
for i in sorted([u'ä',u'c',u'a',u'1',u'ü',u'ß',u'Ä',u'A',u'z',u'Z']):
print i,
1 A Z a c z Ä ß ä ü
Nochwas zu dem Unicode/Bytestring-Problem (Deine Antwort auf HWKs Post). len('ä') ist 2 und len(u'ä') ist 1

Da hab ich mir bei dem kleinen Schnipsel gar keine Gedanken dazu gemacht, mein Fehler. Grundsätzlich wäre meine Antwort hierzu Jein. Welche Fehler man sich zutraut, auf Modulebene zu behandeln, und welche durchzureichen sind, ist meiner Meinung nach eine Fall zu Fall Entscheidung. Im Zweifelsfalle würde ich eher durchreichen, um dem Benutzer die Kontrolle zu gewähren, was aber nicht für alle Fehler gelten sollte, da dies der Einfachheit von Python zuwider läuft und sich der Benutzer mehr Gedanken um Fehlerbehandlung machen muss als zu dem eigentlichen Problem (Da kann er ja gleich Java nehmensnafu hat geschrieben: Zum Auslesen der Terminalgröße: Ich weiß, dass einige Snippets im Netz das so machen, aber ich persönlich hätte lieber eine *Ausnahme*, wenn die Größe nicht ermittelt werden kann. Wie man damit umgeht, kann man dann selbst entscheiden.

Im konkreten Fall würd ich vielleicht so vorgehen: get_size() sollte Fehler präsentieren, da man davon ausgehen kann, dass der Benutzer um den Ressourcenzugriff weiss und damit rechnen muss, dass die Ressource nicht verfügbar ist und entsprechend darauf reagieren kann. (Achtung: der Windowscode ist abgeschrieben, da stecken wahrscheinlich auch noch Fehlermöglichkeiten drin.) column_format() wäre ein solcher Benutzer, die Fehlerbehandlung müsste dann hier erfolgen, sofern man sich nicht entschließt, `width` nur als Parameter zu übergeben (dann eben eine Ebene höher).
Strick Dir dass ruhig nach Deinen Vorstellungen um. Ich lagere normalerweise plattformabhängige Sachen in Untermodule aus und reagiere auf die Plattform via __init__.py.snafu hat geschrieben: Auch das plattformabhängige Importieren hätte ich lieber an den Anfang zu den anderen Imports gesetzt, zwecks Übersicht. Das mag aber alles Geschmackssache sein und lässt sich ja leicht anpassen.
Noch was zum Code. Mit
Code: Alles auswählen
...
from itertools import imap
from string import ljust
...
# Zeile 63
return '\n'.join(sep.join(imap(ljust, it[i::o_rlen] if vertical else
it[i*o_cols:i*o_cols+o_cols], o_col_w))
for i in xrange(o_rlen))

Und weil Du `ls` als Vorbild hattest, wie wärs mit farbigem Output?

Viel Spaß beim Tüfteln.
Zuletzt geändert von jerch am Dienstag 24. November 2009, 21:18, insgesamt 1-mal geändert.
IPython, worauf ich mich beim Testen verlassen habe, sagt:jerch hat geschrieben:len('ä') ist 2 und len(u'ä') ist 1.
Code: Alles auswählen
In [1]: len(u'ä')
Out[1]: 2
Momentan Schulung in neuem Job und demenstprechend wenig Zeit/Kopf für Hobbyprojekte (mache das Programmieren ja nicht beruflich).jerch hat geschrieben:Viel Spaß beim Tüfteln.

Ja, das scheint ein bekannter Inputbug zu sein: https://bugs.launchpad.net/ipython/+bug/339642

Code: Alles auswählen
In [1]: print u'ä'
-------------->print(u'ä')
ä
In [2]: `u'ä'`
Out[2]: "u'\\xc3\\xa4'" # falsch
In [3]: `'ä'`
Out[3]: "'\\xc3\\xa4'"
In [5]: `('ä').decode('utf-8')`
Out[5]: "u'\\xe4'" # das muesste es sein
In [6]: import sys
In [7]: print sys.stdin.encoding, sys.stdout.encoding
------> print(sys.stdin.encoding, sys.stdout.encoding)
('UTF-8', 'UTF-8')

Habe jetzt zwei Module zu dem Thema gefunden:
http://code.google.com/p/prettytable/
http://code.google.com/p/pycolumnize/
...wobei mir `prettytable` dann doch etwas ausgereifter erscheint.
(gibts übrigens auch als Debian-Paket)
http://code.google.com/p/prettytable/
http://code.google.com/p/pycolumnize/
...wobei mir `prettytable` dann doch etwas ausgereifter erscheint.

(gibts übrigens auch als Debian-Paket)
Hab jetzt beide nur mal überflogen. columnize.py macht so ziemlich dasselbe, nur umständlicher. Wäre mal interssant, die beiden zu timern.
prettytable ist aber doch was anderes, wie der Name sagt, zum Tabellendarstellen. Sieht nach ziemlich viel Code aus für so eine (scheinbar?) simple Sache.
prettytable ist aber doch was anderes, wie der Name sagt, zum Tabellendarstellen. Sieht nach ziemlich viel Code aus für so eine (scheinbar?) simple Sache.
Wenn ich von `ls -l` ausgehe und Überschriften haben möchte (die ja nicht zwingend sind) ist es schon ziemlich ähnlich.jerch hat geschrieben:prettytable ist aber doch was anderes, wie der Name sagt, zum Tabellendarstellen.
Für `columnize` wurde als Grundlage die nicht dokumentierte Methode `Cmd.columnize()` aus dem [mod]cmd[/mod] verwendet. Die brauchen da in der Tat viel mehr Code.
`prettytable` hab ich mir im Detail noch nicht angeguckt. Hab erstmal nur gesehen, dass es über 900 Zeilen sind. Hier mag ich vor allem die Flexibilität. Gut vorstellen könnte ich mir das auch für etwas wie `csv2table()`:
Code: Alles auswählen
import csv
from prettytable import PrettyTable
def csv2table(filename):
with open(filename, 'rb') as csvfile:
sample = csvfile.read(1024)
csvfile.seek(0)
sniffer = csv.Sniffer()
dialect = sniffer.sniff(sample)
has_header = sniffer.has_header(sample)
reader = csv.reader(csvfile, dialect)
header = reader.next() if has_header else []
table = PrettyTable(header)
for row in reader:
table.add_row([s.decode('utf-8') for s in row])
return table
def test():
print csv2table('stundenplan.csv').get_string()
Zuletzt geändert von snafu am Freitag 4. Dezember 2009, 05:32, insgesamt 1-mal geändert.
- mkesper
- User
- Beiträge: 919
- Registriert: Montag 20. November 2006, 15:48
- Wohnort: formerly known as mkallas
- Kontaktdaten:
Das Zauberwort könnte locales heißen, siehe http://wiki.python.org/moin/HowTo/Sorting ganz unten.jerch hat geschrieben:Beachte bitte auch, dass sorted wie folgt sortiert:und Du die korrekte deutsche Reihenfolge wahrscheinlich selbst implementieren müsstest.Code: Alles auswählen
for i in sorted([u'ä',u'c',u'a',u'1',u'ü',u'ß',u'Ä',u'A',u'z',u'Z']): print i, 1 A Z a c z Ä ß ä ü
@snafu:
Für prettytable mußt Du doch vorher schon die Spaltenaufteilung kennen, oder?
Für csv ist das eine schöne Sache, mit curses kannst Du gleich mal Multiplan nachbauen
@mkesper:
Ja, das geht mit locale, liefert aber meines Wissens nach nur eine der möglichen Sortiernormen.
Wobei ich das dann eher mit sorted(['zum', 'sortieren'], key=locale.strxfrm) machen würde.
Für prettytable mußt Du doch vorher schon die Spaltenaufteilung kennen, oder?
Für csv ist das eine schöne Sache, mit curses kannst Du gleich mal Multiplan nachbauen

@mkesper:
Ja, das geht mit locale, liefert aber meines Wissens nach nur eine der möglichen Sortiernormen.
Wobei ich das dann eher mit sorted(['zum', 'sortieren'], key=locale.strxfrm) machen würde.
Sorry, ich hab diesen Beitrag wohl übersehen...
Nö, man jagt die Inhalte in die Klasse und hofft, dass die Terminalemulation breit genug für eine vernünftige Ausgabe ist. ;Pjerch hat geschrieben:@snafu:
Für prettytable mußt Du doch vorher schon die Spaltenaufteilung kennen, oder?
Naja, von ``curses`` hab ich auch nicht gesprochen.jerch hat geschrieben:Für csv ist das eine schöne Sache, mit curses kannst Du gleich mal Multiplan nachbauen
Was macht prettytable denn mit einer Liste? Hatte es mir nicht näher angeschaut...snafu hat geschrieben:Nö, man jagt die Inhalte in die Klasse und hofft, dass die Terminalemulation breit genug für eine vernünftige Ausgabe ist. ;P
Da hab ich wohl die Ironietags vergessensnafu hat geschrieben:Naja, von ``curses`` hab ich auch nicht gesprochen.


Grüße jerch
Ach, ich hab dich vermutlich missverstanden. Ja, du musst vorher Feldnamen vergeben, womit für PT dann auch die Anzahl der Spalten fest steht.
Insgesamt ist die Bedienung im Detail aber doch nicht so ganz das, was man von einem guten Tool erwarten würde. Gefühlt bestehen außerdem mindestens 50% des Codes aus "privaten" Getter-/Setter-Funktionen & Fehlerbehandlung. Irgendwie ist das alles schon etwas wirsch gemacht.
Der Autor hat das Projekt auch circa ein 3/4 Jahr nicht mehr angefasst, obwohl den "Issues" nach zu urteilen durchaus ein paar Leute Interesse an dem Projekt haben. Mal sehen, ob sich da noch was tut. Die Standardausgabe finde ich jedenfalls ganz nett gemacht für Fälle, wo man sich mal schnell einen Überblick über eine tabellenartige Struktur verschaffen möchte.
Insgesamt ist die Bedienung im Detail aber doch nicht so ganz das, was man von einem guten Tool erwarten würde. Gefühlt bestehen außerdem mindestens 50% des Codes aus "privaten" Getter-/Setter-Funktionen & Fehlerbehandlung. Irgendwie ist das alles schon etwas wirsch gemacht.
Der Autor hat das Projekt auch circa ein 3/4 Jahr nicht mehr angefasst, obwohl den "Issues" nach zu urteilen durchaus ein paar Leute Interesse an dem Projekt haben. Mal sehen, ob sich da noch was tut. Die Standardausgabe finde ich jedenfalls ganz nett gemacht für Fälle, wo man sich mal schnell einen Überblick über eine tabellenartige Struktur verschaffen möchte.
@sma, HWK:
Ich weiß nicht, ob das Absicht ist, aber eure Programme liefern nicht immer die geringste Zeilenanzahl. Versucht es mal mit, da kommen bei euch 6 Zeilen raus:
Es geht aber auch mit 5 Zeilen, nämlich:
(Das liegt daran, dass es zwei Möglichkeiten mit 4 Spalten gibt, und ihr untersucht in diesem Fall nur die, die nicht klappt...)
Außerdem brecht ihr ab, sobald es nicht mehr klappt, das führt bei zu , obwohl möglich ist.
So, jetzt aber der Code (im Nachhinein eigentlich straight-forward
)
Ich weiß nicht, ob das Absicht ist, aber eure Programme liefern nicht immer die geringste Zeilenanzahl. Versucht es mal mit
Code: Alles auswählen
['x' * 14] * 5 + ['x' * 6] * 11
Code: Alles auswählen
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxx
Code: Alles auswählen
xxxxxxxxxxxxxx xxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
xxxxxxxxxxxxxx xxxxxx xxxxxx
Außerdem brecht ihr ab, sobald es nicht mehr klappt, das führt bei
Code: Alles auswählen
['x', 'x', 'x' * 20, 'x' * 20, 'x', 'x']
Code: Alles auswählen
x
x
xxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxx
x
x
Code: Alles auswählen
x xxxxxxxxxxxxxxxxxxxx x
x xxxxxxxxxxxxxxxxxxxx x
So, jetzt aber der Code (im Nachhinein eigentlich straight-forward

Code: Alles auswählen
def table(seq, sep=" ", line_len=40):
num_items = len(seq)
col_lens = sorted(set(num_items // (i + 1) for i in range(num_items)))
for col_len in col_lens:
cols = [seq[i:i + col_len] for i in range(0, num_items, col_len)]
col_widths = [max(map(len, col)) for col in cols]
width = sum(col_widths) + len(sep) * (len(cols) - 1)
if width < line_len:
break
else:
raise ValueError("Won't fit")
cols[-1].extend([""] * (col_len - len(cols[-1])))
cols = [[item.ljust(w) for item in col] for w, col in zip(col_widths, cols)]
rows = zip(*cols)
return "\n".join(sep.join(row) for row in rows)