Tạo đầu ra cột đẹp trong python


113

Tôi đang cố gắng tạo một danh sách cột đẹp trong python để sử dụng với các công cụ quản trị dòng lệnh mà tôi tạo.

Về cơ bản, tôi muốn một danh sách như:

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

Để biến thành:

a            b            c
aaaaaaaaaa   b            c
a            bbbbbbbbbb   c

Sử dụng các tab đơn giản sẽ không thực hiện thủ thuật ở đây vì tôi không biết dữ liệu dài nhất trong mỗi hàng.

Đây là hành vi tương tự như 'cột -t' trong 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

Tôi đã tìm kiếm các thư viện python khác nhau để thực hiện việc này nhưng không thể tìm thấy bất kỳ thứ gì hữu ích.


4
Sử dụng ncurses hơi quá mức cần thiết để hiển thị ~ 10 dòng thông tin nhỏ mà tôi muốn .. Nhưng chúng tôi đang sử dụng ncurses cho những thứ khác.
xeor

Câu trả lời:


121
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   

Điều này làm là tính toán mục nhập dữ liệu dài nhất để xác định chiều rộng cột, sau đó sử dụng .ljust()để thêm phần đệm cần thiết khi in ra từng cột.


1
Tên longestgây hiểu lầm vì nó không phải là phần tử dài nhất mà là max_length. BTW lâu nhất có thể được thực hiện với một cái gì đó như: max((w for sub in data for w in sub), key=len). [Tái bút tôi không phải là người phản đối]
Rik Poggi

1
max((w for ...), key=len)cung cấp cho bạn mục nhập dài nhất và sau đó bạn sẽ cần chạy lenlại. Không thể quyết định cái nào rõ ràng nên tôi mắc kẹt với cái đầu tiên. Điểm tốt ở tên var gây hiểu lầm. Đã thay đổi.
Shawn Chin

1
Vâng, không có sự khác biệt lớn với cái này hay cái kia, chỉ là vấn đề khẩu vị mà tôi muốn nói. Ngoài ra, như bạn nhận thấy, dòng đó hơi (quá) nhầm lẫn. Sẽ tốt hơn nếu làm điều đó trực tiếp : max(len(x) for sub in data for x in sub), điều đó cũng không tạo ra các danh sách không cần thiết.
Rik Poggi

1
Cảm ơn! Đây chính là thứ tôi cần. Tuy nhiên, tôi cũng phải làm cho nó hoạt động với python 2.4, vì vậy tôi đã loại bỏ chain.from_iterable và thay thế col_width bằng max (len (x) cho sub trong dữ liệu cho x trong sub) + 2 như được đề xuất. Hy vọng bạn có thể thay đổi mã của mình ở trên để làm rõ nếu người khác muốn điều này chạy với 2.4.
xeor

1
Điều này sẽ làm cho tất cả các cột có cùng chiều rộng, điều này không column -tgiống nhau.
trực giác

145

Kể từ Python 2.6+, bạn có thể sử dụng chuỗi định dạng theo cách sau để đặt các cột thành tối thiểu 20 ký tự và căn chỉnh văn bản sang phải.

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

Đầu ra:

               a                    b                    c
      aaaaaaaaaa                    b                    c
               a           bbbbbbbbbb                    c

10
cho đến nay là giải pháp tốt nhất cho đến thời điểm hiện tại
zlr

Điều này chỉ hiển thị 9 mục khi tôi cố gắng sử dụng nó.
Dorian Dore

Và bạn chỉ có thể tiếp tục bổ sung {: >20}để hiển thị thêm các lĩnh vực
PercussiveRepair

8
Thêm vào giải pháp từ KurzedMetal, trong công cụ định dạng được hiển thị ở trên; {:> 20}, dấu> cho biết căn chỉnh phải. Bằng cách sử dụng {: <20}, bạn sẽ nhận được một cột căn trái và bằng cách sử dụng {: ^ 20}, bạn sẽ nhận được một cột căn giữa.
Dale Moore

1
Tôi không nghĩ rằng điều này trả lời được câu hỏi — có vẻ như OP muốn làm cho mỗi hàng không rộng hơn mức cần thiết để chứa nội dung của nó. Điều này chỉ đặt chiều rộng cố định là 20.
trực giác

43

Tôi đến đây với các yêu cầu tương tự nhưng câu trả lời của @lvc và @ Preet có vẻ phù hợp hơn với những gì column -ttạo ra trong các cột đó có độ rộng khác nhau:

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

2
Đẹp. Đây là giải pháp rõ ràng nhất mà thực sự tuân theo "thông số kỹ thuật" ban đầu.
trực giác

2
Đây là giải pháp đã làm việc cho tôi. Các giải pháp khác tạo ra đầu ra dạng cột nhưng giải pháp này cho phép kiểm soát nhiều nhất vùng đệm cùng với độ rộng cột chính xác.
Michael J

1
Giải pháp đẹp. Đối với bất kỳ cột không dây, chỉ cần thêm một bản đồ thêm: map(len, map(str, col)).
Druckles

11

Đến bữa tiệc là hơi muộn, và một sự lựa chọn không biết xấu hổ cho một gói mà tôi đã viết, nhưng bạn cũng có thể xem gói Columnar .

Nó có một danh sách các danh sách đầu vào và danh sách các tiêu đề và xuất ra một chuỗi có định dạng bảng. Đoạn mã này tạo một bảng docker-esque:

from columnar import columnar

headers = ['name', 'id', 'host', 'notes']

data = [
    ['busybox', 'c3c37d5d-38d2-409f-8d02-600fd9d51239', 'linuxnode-1-292735', 'Test server.'],
    ['alpine-python', '6bb77855-0fda-45a9-b553-e19e1a795f1e', 'linuxnode-2-249253', 'The one that runs python.'],
    ['redis', 'afb648ba-ac97-4fb2-8953-9a5b5f39663e', 'linuxnode-3-3416918', 'For queues and stuff.'],
    ['app-server', 'b866cd0f-bf80-40c7-84e3-c40891ec68f9', 'linuxnode-4-295918', 'A popular destination.'],
    ['nginx', '76fea0f0-aa53-4911-b7e4-fae28c2e469b', 'linuxnode-5-292735', 'Traffic Cop'],
]

table = columnar(data, headers, no_borders=True)
print(table)

Bảng Hiển thị Kiểu không viền

Hoặc bạn có thể bắt mắt hơn một chút với màu sắc và đường viền. Bảng trưng bày các tác phẩm kinh điển mùa xuân

Để đọc thêm về thuật toán định cỡ cột và xem phần còn lại của API, bạn có thể xem liên kết ở trên hoặc xem Columnar GitHub Repo


gói này hoạt động tốt, cảm ơn vì đã chia sẻ
wonton

8

Bạn phải làm điều này với 2 lần vượt qua:

  1. lấy chiều rộng tối đa của mỗi cột.
  2. định dạng các cột bằng kiến ​​thức của chúng tôi về chiều rộng tối đa từ lần vượt qua đầu tiên bằng cách sử dụng str.ljust()str.rjust()

7

Việc chuyển đổi các cột như vậy là một công việc đối với zip:

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

Để tìm độ dài cần thiết của mỗi cột, bạn có thể sử dụng max:

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

Bạn có thể sử dụng cái nào, với phần đệm thích hợp, để tạo chuỗi để chuyển tới 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'

6

Để có được những bảng đẹp hơn như

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

bạn có thể sử dụng công thức Python này :

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

Các trang recipe Python chứa một vài cải tiến trên đó.


5

pandas giải pháp dựa trên tạo khung dữ liệu:

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

Để loại bỏ các giá trị chỉ mục và tiêu đề để tạo ra những gì bạn muốn, bạn có thể sử dụng to_stringphương pháp:

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

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

1

Scolp là một thư viện mới cho phép bạn in dữ liệu cột trực tuyến một cách dễ dàng trong khi tự động điều chỉnh độ rộng cột.

(Tuyên bố từ chối trách nhiệm: Tôi là tác giả)


1

Điều này đặt độ rộng cột độc lập, phù hợp nhất dựa trên chỉ số tối đa được sử dụng trong các câu trả lời khác.

data = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']]
padding = 2
col_widths = [max(len(w) for w in [r[cn] for r in data]) + padding for cn in range(len(data[0]))]
format_string = "{{:{}}}{{:{}}}{{:{}}}".format(*col_widths)
for row in data:
    print(format_string.format(*row))

Đây là câu trả lời cao cấp, mặc dù mã hơi nặng mùi.
Sam Morgan

1

Dành cho những người lười biếng

đang sử dụng Python 3. *Pandas / Geopandas ; phương pháp tiếp cận trong lớp đơn giản phổ biến (đối với tập lệnh 'bình thường' chỉ cần xóa tự ):

Chức năng tô màu:

    def colorize(self,s,color):
        s = color+str(s)+"\033[0m"
        return s

Tiêu đề:

print('{0:<23} {1:>24} {2:>26} {3:>26} {4:>11} {5:>11}'.format('Road name','Classification','Function','Form of road','Length','Distance') )

và sau đó là dữ liệu từ khung dữ liệu Pandas / Geopandas:

            for index, row in clipped.iterrows():
                rdName      = self.colorize(row['name1'],"\033[32m")
                rdClass     = self.colorize(row['roadClassification'],"\033[93m")
                rdFunction  = self.colorize(row['roadFunction'],"\033[33m")
                rdForm      = self.colorize(row['formOfWay'],"\033[94m")
                rdLength    = self.colorize(row['length'],"\033[97m")
                rdDistance  = self.colorize(row['distance'],"\033[96m")
                print('{0:<30} {1:>35} {2:>35} {3:>35} {4:>20} {5:>20}'.format(rdName,rdClass,rdFunction,rdForm,rdLength,rdDistance) )

Ý nghĩa của {0:<30} {1:>35} {2:>35} {3:>35} {4:>20} {5:>20}:

0, 1, 2, 3, 4, 5 -> cột, có tổng cộng 6 trong trường hợp này

30, 35, 20-> chiều rộng của cột (lưu ý rằng bạn sẽ phải thêm chiều dài \033[96m- điều này đối với Python cũng là một chuỗi), chỉ cần thử nghiệm :)

>, <-> justify: right, left (cũng có =để điền các số không)

Nếu bạn muốn phân biệt, ví dụ như giá trị tối đa, bạn sẽ phải chuyển sang chức năng kiểu Pandas đặc biệt, nhưng giả sử điều đó đủ xa để hiển thị dữ liệu trên cửa sổ đầu cuối.

Kết quả:

nhập mô tả hình ảnh ở đây


1

Một chút khác biệt về câu trả lời trước đó (tôi không có đủ đại diện để bình luận về nó). Thư viện định dạng cho phép bạn chỉ định chiều rộng và căn chỉnh của một phần tử nhưng không phải nơi nó bắt đầu, tức là bạn có thể nói "rộng 20 cột" nhưng không phải "bắt đầu ở cột 20". Dẫn đến vấn đề này:

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

print("first row: {: >20} {: >20} {: >20}".format(*table_data[0]))
print("second row: {: >20} {: >20} {: >20}".format(*table_data[1]))
print("third row: {: >20} {: >20} {: >20}".format(*table_data[2]))

Đầu ra

first row:                    a                    b                    c
second row:           aaaaaaaaaa                    b                    c
third row:                    a           bbbbbbbbbb                    c

Tất nhiên câu trả lời là định dạng các chuỗi theo nghĩa đen, điều này kết hợp hơi kỳ lạ với định dạng:

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

print(f"{'first row:': <20} {table_data[0][0]: >20} {table_data[0][1]: >20} {table_data[0][2]: >20}")
print("{: <20} {: >20} {: >20} {: >20}".format(*['second row:', *table_data[1]]))
print("{: <20} {: >20} {: >20} {: >20}".format(*['third row:', *table_data[1]]))

Đầu ra

first row:                              a                    b                    c
second row:                    aaaaaaaaaa                    b                    c
third row:                     aaaaaaaaaa                    b                    c

0

Tôi thấy câu trả lời này siêu hữu ích và trang nhã, ban đầu từ đây :

matrix = [["A", "B"], ["C", "D"]]

print('\n'.join(['\t'.join([str(cell) for cell in row]) for row in matrix]))

Đầu ra

A   B
C   D

0

Đây là một biến thể của câu trả lời của Shawn Chin. Chiều rộng được cố định trên mỗi cột, không phải trên tất cả các cột. Ngoài ra còn có một đường viền bên dưới hàng đầu tiên và giữa các cột. ( thư viện icontract được sử dụng để thực thi các hợp đồng.)

@icontract.pre(
    lambda table: not table or all(len(row) == len(table[0]) for row in table))
@icontract.post(lambda table, result: result == "" if not table else True)
@icontract.post(lambda result: not result.endswith("\n"))
def format_table(table: List[List[str]]) -> str:
    """
    Format the table as equal-spaced columns.

    :param table: rows of cells
    :return: table as string
    """
    cols = len(table[0])

    col_widths = [max(len(row[i]) for row in table) for i in range(cols)]

    lines = []  # type: List[str]
    for i, row in enumerate(table):
        parts = []  # type: List[str]

        for cell, width in zip(row, col_widths):
            parts.append(cell.ljust(width))

        line = " | ".join(parts)
        lines.append(line)

        if i == 0:
            border = []  # type: List[str]

            for width in col_widths:
                border.append("-" * width)

            lines.append("-+-".join(border))

    result = "\n".join(lines)

    return result

Đây là một ví dụ:

>>> table = [['column 0', 'another column 1'], ['00', '01'], ['10', '11']]
>>> result = packagery._format_table(table=table)
>>> print(result)
column 0 | another column 1
---------+-----------------
00       | 01              
10       | 11              

0

đã cập nhật @Franck Dernoncourt công thức ưa thích để tương thích với python 3 và PEP8

import io
import math
import operator
import re
import functools

from itertools import zip_longest


def indent(
    rows,
    has_header=False,
    header_char="-",
    delim=" | ",
    justify="left",
    separate_rows=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 row_wrapper(row):
        new_rows = [wrapfunc(item).split("\n") for item in row]
        return [[substr or "" for substr in item] for item in zip_longest(*new_rows)]

    # break each logical row into one or more physical ones
    logical_rows = [row_wrapper(row) for row in rows]
    # columns of physical rows
    columns = zip_longest(*functools.reduce(operator.add, logical_rows))
    # get the maximum of each column by the string length of its items
    max_widths = [max([len(str(item)) for item in column]) for column in columns]
    row_separator = header_char * (
        len(prefix) + len(postfix) + sum(max_widths) + len(delim) * (len(max_widths) - 1)
    )
    # select the appropriate justify method
    justify = {"center": str.center, "right": str.rjust, "left": str.ljust}[
        justify.lower()
    ]
    output = io.StringIO()
    if separate_rows:
        print(output, row_separator)
    for physicalRows in logical_rows:
        for row in physicalRows:
            print( output, prefix + delim.join(
                [justify(str(item), width) for (item, width) in zip(row, max_widths)]
            ) + postfix)
        if separate_rows or has_header:
            print(output, row_separator)
            has_header = 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 functools.reduce(
        lambda line, word, i_width=width: "%s%s%s"
        % (
            line,
            " \n"[
                (
                    len(line[line.rfind("\n") + 1 :]) + len(word.split("\n", 1)[0])
                    >= i_width
                )
            ],
            word,
        ),
        text.split(" "),
    )


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


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 range(int(math.ceil(1.0 * 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, has_header=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,
                has_header=True,
                separate_rows=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 |
    # ---------------------------------------------

-1

Tôi nhận ra câu hỏi này đã cũ nhưng tôi không hiểu câu trả lời của Antak và không muốn sử dụng thư viện nên tôi đã đưa ra giải pháp của riêng mình.

Giải pháp giả định các bản ghi là một mảng 2D, các bản ghi đều có cùng độ dài và các trường đều là chuỗi.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.