2012-04-03 4 views
64

Ich versuche, eine nette Spaltenliste in Python für die Verwendung mit Kommandozeilen-Admin-Tools, die ich erstellen.Erstellen Sie schöne Spaltenausgabe in Python

Basicly, möchte ich eine Liste wie:

[['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] 

So schalten Sie in:

a   b   c 
aaaaaaaaaa b   c 
a   bbbbbbbbbb c 

Ebene Tabs tun pflegt den Trick hier, weil ich in jeder Reihe die längsten Daten nicht kennen .

Dies ist das gleiche Verhalten wie ‚Spalte -t‘ in Linux ..

$ echo -e "a b c\naaaaaaaaaa b c\na bbbbbbbbbb c" 
a b c 
aaaaaaaaaa b c 
a bbbbbbbbbb c 

$ echo -e "a b c\naaaaaaaaaa b c\na bbbbbbbbbb c" | column -t 
a   b   c 
aaaaaaaaaa b   c 
a   bbbbbbbbbb c 

ich ausgesehen haben um für verschiedene Python-Bibliotheken, dies zu tun, kann aber nichts Brauchbares finden.

+0

Was ist mit der Verwendung von Ncurses? – sherpya

+3

Die Verwendung von Ncurses ist ein wenig Overkill für die Anzeige der kleinen ~ 10 Zeilen von Informationen, die ich will. Aber wir verwenden Ncurses für andere Sachen. – xeor

Antwort

76
data = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] 

col_width = max(len(word) for row in data for word in row) + 2 # padding 
for row in data: 
    print "".join(word.ljust(col_width) for word in row) 

a   b   c    
aaaaaaaaaa b   c    
a   bbbbbbbbbb c 

Was dies bedeutet ist die längste Dateneingabe berechnet die Spaltenbreite, um zu bestimmen, dann .ljust() verwendet die hinzufügen notwendiges Auffüllen beim Ausdrucken jeder Spalte.

+1

Der Name 'longest' ist irreführend, da es nicht das längste Element ist, sondern die max_length. BTW der längste könnte mit etwas wie genommen werden: 'max ((w für sub in Daten für w in sub), key = len)'. [P.S. Ich war nicht derjenige, der downvote] –

+1

'max ((w für ...), key = len)' gibt Ihnen den längsten Eintrag und Sie müssen dann einen Lauf 'len' erneut tun. Ich konnte nicht entscheiden, was klar war, also blieb ich bei der ersten. Guter Punkt auf den irreführenden Var-Namen. Geändert. –

+1

Ja, es gibt keinen großen Unterschied mit dem einen oder dem anderen, nur eine Frage des Geschmacks, würde ich sagen. Abgesehen davon, wie Sie bemerkt haben, ist diese Linie ein wenig (zu) verwirrt. Es wäre besser, es direkt zu tun: 'max (len (x) für sub in Daten für x in sub)', das auch nicht unnötige Listen baut. –

7

Sie haben dies mit zwei Pässe zu tun:

  1. die maximale Breite jeder Spalte erhalten.
  2. die Spalten Formatierung unseres Wissens über max Breite von dem ersten Durchlauf unter Verwendung von mit str.ljust() und str.rjust()
7

die Spalten wie das ist ein Job für zip Transponieren:

>>> a = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] 
>>> list(zip(*a)) 
[('a', 'aaaaaaaaaa', 'a'), ('b', 'b', 'bbbbbbbbbb'), ('c', 'c', 'c')] 

Um die erforderliche Länge jeder Spalte zu finden, können Sie max:

>>> trans_a = zip(*a) 
>>> [max(len(c) for c in b) for b in trans_a] 
[10, 10, 1] 

die Sie verwenden können, mit geeigneten padding, um Strings zu erzeugen, die an print übergeben werden:

>>> col_lenghts = [max(len(c) for c in b) for b in trans_a] 
>>> padding = ' ' # You might want more 
>>> padding.join(s.ljust(l) for s,l in zip(a[0], col_lenghts)) 
'a   b   c' 
75

In Python 2.6+, kann die folgende format string verwendet werden, um die Spalten auf ein Minimum von 20 Zeichen festzulegen und Text nach rechts auszurichten.

>>> table_data = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] 
>>> for row in table_data: 
...  print("{: >20} {: >20} {: >20}".format(*row)) 
... 
        a     b     c 
      aaaaaaaaaa     b     c 
        a   bbbbbbbbbb     c 
+1

bei weitem die beste Lösung ab sofort – zlr

+0

Dies zeigt nur 9 Elemente, wenn ich versuchte, es zu verwenden. –

+0

Und Sie können einfach '{:> 20}' hinzufügen, um mehr Felder anzuzeigen – PercussiveRepair

28

Ich kam hier mit den gleichen Anforderungen aber @lvc und @ Antworten Preet die scheint mehr inline mit dem, was column -t in produziert, dass Spalten unterschiedliche Breiten:

>>> rows = [ ['a',   'b',   'c', 'd'] 
...   , ['aaaaaaaaaa', 'b',   'c', 'd'] 
...   , ['a',   'bbbbbbbbbb', 'c', 'd'] 
...   ] 
... 

>>> widths = [max(map(len, col)) for col in zip(*rows)] 
>>> for row in rows: 
...  print " ".join((val.ljust(width) for val, width in zip(row, widths))) 
... 
a   b   c d 
aaaaaaaaaa b   c d 
a   bbbbbbbbbb c d 
+1

Schön. Dies ist die klarste Lösung, die tatsächlich der ursprünglichen "Spezifikation" folgt. – intuited

+1

Das ist die Lösung, die für mich funktioniert hat. Andere Lösungen erzeugten eine Spaltenausgabe, aber diese gab die meiste Kontrolle über die Auffüllung zusammen mit genauen Spaltenbreiten. –

+0

Dies sollte die akzeptierte Lösung sein. –

4

Um Züchtertabellen wie

--------------------------------------------------- 
| First Name | Last Name  | Age | Position | 
--------------------------------------------------- 
| John  | Smith   | 24 | Software | 
|   |     |  | Engineer | 
--------------------------------------------------- 
| Mary  | Brohowski  | 23 | Sales  | 
|   |     |  | Manager | 
--------------------------------------------------- 
| Aristidis | Papageorgopoulos | 28 | Senior | 
|   |     |  | Reseacher | 
--------------------------------------------------- 

können Sie diese Python recipe verwenden:

''' 
From http://code.activestate.com/recipes/267662-table-indentation/ 
PSF License 
''' 
import cStringIO,operator 

def indent(rows, hasHeader=False, headerChar='-', delim=' | ', justify='left', 
      separateRows=False, prefix='', postfix='', wrapfunc=lambda x:x): 
    """Indents a table by column. 
     - rows: A sequence of sequences of items, one sequence per row. 
     - hasHeader: True if the first row consists of the columns' names. 
     - headerChar: Character to be used for the row separator line 
     (if hasHeader==True or separateRows==True). 
     - delim: The column delimiter. 
     - justify: Determines how are data justified in their column. 
     Valid values are 'left','right' and 'center'. 
     - separateRows: True if rows are to be separated by a line 
     of 'headerChar's. 
     - prefix: A string prepended to each printed row. 
     - postfix: A string appended to each printed row. 
     - wrapfunc: A function f(text) for wrapping text; each element in 
     the table is first wrapped by this function.""" 
    # closure for breaking logical rows to physical, using wrapfunc 
    def rowWrapper(row): 
     newRows = [wrapfunc(item).split('\n') for item in row] 
     return [[substr or '' for substr in item] for item in map(None,*newRows)] 
    # break each logical row into one or more physical ones 
    logicalRows = [rowWrapper(row) for row in rows] 
    # columns of physical rows 
    columns = map(None,*reduce(operator.add,logicalRows)) 
    # get the maximum of each column by the string length of its items 
    maxWidths = [max([len(str(item)) for item in column]) for column in columns] 
    rowSeparator = headerChar * (len(prefix) + len(postfix) + sum(maxWidths) + \ 
           len(delim)*(len(maxWidths)-1)) 
    # select the appropriate justify method 
    justify = {'center':str.center, 'right':str.rjust, 'left':str.ljust}[justify.lower()] 
    output=cStringIO.StringIO() 
    if separateRows: print >> output, rowSeparator 
    for physicalRows in logicalRows: 
     for row in physicalRows: 
      print >> output, \ 
       prefix \ 
       + delim.join([justify(str(item),width) for (item,width) in zip(row,maxWidths)]) \ 
       + postfix 
     if separateRows or hasHeader: print >> output, rowSeparator; hasHeader=False 
    return output.getvalue() 

# written by Mike Brown 
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 
def wrap_onspace(text, width): 
    """ 
    A word-wrap function that preserves existing line breaks 
    and most spaces in the text. Expects that existing line 
    breaks are posix newlines (\n). 
    """ 
    return reduce(lambda line, word, width=width: '%s%s%s' % 
        (line, 
        ' \n'[(len(line[line.rfind('\n')+1:]) 
         + len(word.split('\n',1)[0] 
          ) >= width)], 
        word), 
        text.split(' ') 
       ) 

import re 
def wrap_onspace_strict(text, width): 
    """Similar to wrap_onspace, but enforces the width constraint: 
     words longer than width are split.""" 
    wordRegex = re.compile(r'\S{'+str(width)+r',}') 
    return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(),width),text),width) 

import math 
def wrap_always(text, width): 
    """A simple word-wrap function that wraps text on exactly width characters. 
     It doesn't split the text in words.""" 
    return '\n'.join([ text[width*i:width*(i+1)] \ 
         for i in xrange(int(math.ceil(1.*len(text)/width))) ]) 

if __name__ == '__main__': 
    labels = ('First Name', 'Last Name', 'Age', 'Position') 
    data = \ 
    '''John,Smith,24,Software Engineer 
     Mary,Brohowski,23,Sales Manager 
     Aristidis,Papageorgopoulos,28,Senior Reseacher''' 
    rows = [row.strip().split(',') for row in data.splitlines()] 

    print 'Without wrapping function\n' 
    print indent([labels]+rows, hasHeader=True) 
    # test indent with different wrapping functions 
    width = 10 
    for wrapper in (wrap_always,wrap_onspace,wrap_onspace_strict): 
     print 'Wrapping function: %s(x,width=%d)\n' % (wrapper.__name__,width) 
     print indent([labels]+rows, hasHeader=True, separateRows=True, 
        prefix='| ', postfix=' |', 
        wrapfunc=lambda x: wrapper(x,width)) 

    # output: 
    # 
    #Without wrapping function 
    # 
    #First Name | Last Name  | Age | Position   
    #------------------------------------------------------- 
    #John  | Smith   | 24 | Software Engineer 
    #Mary  | Brohowski  | 23 | Sales Manager  
    #Aristidis | Papageorgopoulos | 28 | Senior Reseacher 
    # 
    #Wrapping function: wrap_always(x,width=10) 
    # 
    #---------------------------------------------- 
    #| First Name | Last Name | Age | Position | 
    #---------------------------------------------- 
    #| John  | Smith  | 24 | Software E | 
    #|   |   |  | ngineer | 
    #---------------------------------------------- 
    #| Mary  | Brohowski | 23 | Sales Mana | 
    #|   |   |  | ger  | 
    #---------------------------------------------- 
    #| Aristidis | Papageorgo | 28 | Senior Res | 
    #|   | poulos  |  | eacher  | 
    #---------------------------------------------- 
    # 
    #Wrapping function: wrap_onspace(x,width=10) 
    # 
    #--------------------------------------------------- 
    #| First Name | Last Name  | Age | Position | 
    #--------------------------------------------------- 
    #| John  | Smith   | 24 | Software | 
    #|   |     |  | Engineer | 
    #--------------------------------------------------- 
    #| Mary  | Brohowski  | 23 | Sales  | 
    #|   |     |  | Manager | 
    #--------------------------------------------------- 
    #| Aristidis | Papageorgopoulos | 28 | Senior | 
    #|   |     |  | Reseacher | 
    #--------------------------------------------------- 
    # 
    #Wrapping function: wrap_onspace_strict(x,width=10) 
    # 
    #--------------------------------------------- 
    #| First Name | Last Name | Age | Position | 
    #--------------------------------------------- 
    #| John  | Smith  | 24 | Software | 
    #|   |   |  | Engineer | 
    #--------------------------------------------- 
    #| Mary  | Brohowski | 23 | Sales  | 
    #|   |   |  | Manager | 
    #--------------------------------------------- 
    #| Aristidis | Papageorgo | 28 | Senior | 
    #|   | poulos  |  | Reseacher | 
    #--------------------------------------------- 

Die Python recipe page enthält einige Verbesserungen daran.

5

pandas basierte Lösung mit dem Erstellen von Datenrahmen:

import pandas as pd 
l = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] 
df = pd.DataFrame(l) 

print(df) 
      0   1 2 
0   a   b c 
1 aaaaaaaaaa   b c 
2   a bbbbbbbbbb c 

Index und Header zu entfernen ausgegebenen Werte zu schaffen, was Sie wollen, Sie to_string Methode verwenden:

result = df.to_string(index=False, header=False) 

print(result) 
      a   b c 
aaaaaaaaaa   b c 
      a bbbbbbbbbb c 
0

Ich weiß, diese Frage ist alt, aber Ich habe Antaks Antwort nicht verstanden und wollte keine Bibliothek benutzen, also habe ich meine eigene Lösung gerollt.

Die Lösung geht davon aus, dass es sich bei den Datensätzen um ein 2D-Array handelt, Datensätze haben alle dieselbe Länge und alle Felder sind Strings.

def stringifyRecords(records): 
    column_widths = [0] * len(records[0]) 
    for record in records: 
     for i, field in enumerate(record): 
      width = len(field) 
      if width > column_widths[i]: column_widths[i] = width 

    s = "" 
    for record in records: 
     for column_width, field in zip(column_widths, record): 
      s += field.ljust(column_width+1) 
     s += "\n" 

    return s