2012-04-03 15 views
64

Sto cercando di creare una bella lista di colonne in python da usare con gli strumenti di amministrazione della riga di comando che creo.Creare una bella colonna di output in python

Fondamentalmente, voglio una lista come:

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

a trasformarsi in:

a   b   c 
aaaaaaaaaa b   c 
a   bbbbbbbbbb c 

Utilizzando schede di pianura non farà il trucco qui perché non so i dati più lunghe in ogni riga .

Questo è lo stesso comportamento come 'colonna -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 

Ho guardato in giro per le varie librerie Python per fare questo, ma non riesco a trovare qualcosa di utile.

+0

che dire sull'uso di ncurses? – sherpya

+3

L'uso di ncurses è un po 'eccessivo per visualizzare le piccole ~ 10 righe di informazioni che voglio ... Ma stiamo usando ncurses per altre cose. – xeor

risposta

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 

Quello che fa è calcolare l'immissione dei dati più lungo per determinare la larghezza della colonna, quindi utilizzare .ljust() per aggiungere la riempimento necessario durante la stampa di ogni colonna.

+1

Il nome "più lungo" è fuorviante perché non è l'elemento più lungo ma il massimo. BTW il più lungo potrebbe essere preso con qualcosa del tipo: 'max ((w per dati secondari per w in sub), key = len)'. [P.S. Io non ero quello a downvote] –

+1

'max ((w per ...), key = len)' ti dà la voce più lunga e dovrai quindi eseguire di nuovo 'len' di nuovo. Non potevo decidere quale era chiaro, quindi mi sono bloccato con il primo. Buon punto sul nome var fuorviante. Cambiato. –

+1

Sì, non c'è una grande differenza con l'una o l'altra, solo una questione di gusto, direi. A parte questo, come hai notato, quella linea è un po '(troppo) confusa. Sarebbe meglio farlo direttamente: 'max (len (x) per i dati secondari per x in sub)', che inoltre non crea liste non necessarie. –

7

Devi fare questo con 2 pass:

  1. ottenere la larghezza massima di ogni colonna.
  2. formattare le colonne utilizzando la nostra conoscenza di larghezza massima dal primo passaggio utilizzando str.ljust() e str.rjust()
7

recepiscono le colonne come questo è un lavoro per zip:

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

per trovare la lunghezza necessaria di ogni colonna, è possibile utilizzare max:

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

che è possibile utilizzare, con opportuni imbottitura, per costruire stringhe da passare al print:

>>> 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+, il seguente format string può essere utilizzato per impostare le colonne su un minimo di 20 caratteri e allineare il testo a destra.

>>> 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

di gran lunga la migliore soluzione di adesso – zlr

+0

Questo mostra sempre solo 9 elementi quando ho provato ad usarlo. –

+0

E puoi semplicemente continuare ad aggiungere '{:> 20}' per visualizzare più campi – PercussiveRepair

28

Sono venuto qui con gli stessi requisiti, ma @lvc e @ risposte di Preet sembra più in linea con quello che column -t produce in quel colonne hanno diverse larghezze:

>>> 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

Bello. Questa è la soluzione più chiara che segue effettivamente le "specifiche" originali. – intuited

+1

Questa è la soluzione che ha funzionato per me. Altre soluzioni hanno prodotto un output colonnare, ma questo ha dato il massimo controllo sull'imbottitura insieme a precise larghezze delle colonne. –

+0

Questa dovrebbe essere la soluzione accettata. –

4

Per ottenere tavoli più elaborati come

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

è possibile utilizzare questo Python recipe:

''' 
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 | 
    #--------------------------------------------- 

Il Python recipe page contiene alcuni miglioramenti su di esso.

5

pandas soluzione basata con dataframe creazione:

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 

Per rimuovere i valori di indice e intestazione per creare uscita ciò che si vuole è possibile utilizzare to_string metodo:

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

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

Mi rendo conto che questa domanda è vecchio ma Non ho capito la risposta di Antak e non volevo usare una libreria, quindi ho ottenuto la mia soluzione.

La soluzione presuppone che i record siano un array 2D, i record siano tutti della stessa lunghezza e che i campi siano tutte stringhe.

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 
Problemi correlati