How to print a list more nicely?

Viewed 62098

This is similar to How to print a list in Python “nicely”, but I would like to print the list even more nicely -- without the brackets and apostrophes and commas, and even better in columns.

foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf', 
    'pdcurses-devel',     'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel', 
    'qgis1.1', 'php_mapscript']

evenNicerPrint(foolist)

Desired result:

exiv2-devel       msvcrt        
mingw-libs        gdal-grass    
tcltk-demos       iconv         
fcgi              qgis-devel    
netcdf            qgis1.1       
pdcurses-devel    php_mapscript 

thanks!

24 Answers

Although not designed for it, the standard-library module in Python 3 cmd has a utility for printing a list of strings in multiple columns

import cmd
cli = cmd.Cmd()
cli.columnize(foolist, displaywidth=80)

You even then have the option of specifying the output location, with cmd.Cmd(stdout=my_stream)

I use IPython internal columnize function

import IPython
foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf', 
           'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel', 
           'qgis1.1', 'php_mapscript']

foolist_columnized = IPython.utils.text.columnize(foolist)
print(foolist_columnized)

output will look like:

exiv2-devel  tcltk-demos  netcdf          msvcrt      iconv       qgis1.1
mingw-libs   fcgi         pdcurses-devel  gdal-grass  qgis-devel  php_mapscript
[print('{:20}'.format(key), end='\t') if (idx + 1) % 5 else print(key, end='\n') for idx, key in enumerate(list_variable)]

or

for idx, key in enumerate(list_variable):
    if (idx + 1) % 5:
        print('{:20}'.format(key), end='\t')
    else:
        print(key, end='\n')

There are tons of answers already, but I will share my solution, which in addition to printing the list into multiple columns, it also chooses the amount of columns dynamically, from the terminal width and the longest string on the list.

import os
cols = os.popen('stty size', 'r').read().split()[1]

def print_multicol(my_list):
    max_len = len(max(my_list,key=len)) + 2
    ncols = (int(cols) -4 ) / max_len
    while my_list:
        n = 0
        while n < ncols:
            if len(my_list) > 0 :
                fstring = "{:<"+str(max_len)+"}"
                print fstring.format(my_list.pop(0)),
            n += 1
        print

a_list = "a ab abc abcd abcde b bc bcde bcdef c cde cdef cdfg d de defg"
a_list += "defgh e ef efg efghi efghij f fg fgh fghij fghijk"

print_multicol(a_list.split())

As an expansion of @Aman below is a function which takes a list of strings and outputs them in columns based on the terminal size.

import os
def column_display(input_list):
    '''
    Used to create a structured column display based on the users terminal size

    input_list : A list of string items which is desired to be displayed
    '''
    rows, columns = os.popen('stty size', 'r').read().split()
    terminal_space_eighth = int(columns)/8
    terminal_space_seventh = int(columns)/7
    terminal_space_sixth = int(columns)/6
    terminal_space_fifth = int(columns)/5
    terminal_space_quarter = int(columns)/4
    terminal_space_third = int(columns)/3
    terminal_space_half = int(columns)/2
    longest_string = max(input_list, key=len)
    longest_length = len(longest_string) + 1
    list_size = len(input_list)

    if longest_length > terminal_space_half:
         for string in input_list:
             print(string)
    elif terminal_space_eighth >= longest_length and list_size >= 8:
         for a,b,c,d,e,f,g,h in zip(input_list[::8],input_list[1::8],input_list[2::8], input_list[3::8], input_list[4::8], input_list[5::8], input_list[6::8], input_list[7::8]):
             column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length, longest_length, longest_length )
             output = column_space.format(a,b,c,d,e,f,g,h)
             print(output)
    elif terminal_space_seventh >= longest_length and list_size >= 7:
        for a,b,c,d,e,f,g in zip(input_list[::7],input_list[1::7],input_list[2::7], input_list[3::7], input_list[4::7], input_list[5::7], input_list[6::7]):
             column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length, longest_length)
             output = column_space.format(a,b,c,d,e,f,g)
             print(output)
    elif terminal_space_sixth >= longest_length and list_size >= 6:
         for a,b,c,d,e,f in zip(input_list[::6],input_list[1::6],input_list[2::6], input_list[3::6], input_list[4::6], input_list[5::6]):
             column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length, longest_length)
             output = column_space.format(a,b,c,d,e,f)
             print(output)
    elif terminal_space_fifth >= longest_length and list_size >= 5:
        for a,b,c,d,e in zip(input_list[::5],input_list[1::5],input_list[2::5], input_list[3::5], input_list[4::5]):
            column_space = '{:<%s}{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length, longest_length)
            output = column_space.format(a,b,c,d,e)
            print(output)
    elif terminal_space_quarter >= longest_length and list_size >= 4:
        for a,b,c,d in zip(input_list[::4],input_list[1::4],input_list[2::4], input_list[3::4]):
            column_space = '{:<%s}{:<%s}{:<%s}{:<}' % (longest_length, longest_length, longest_length)
            output = column_space.format(a,b,c,d)
            print(output)
    elif terminal_space_third >= longest_length and list_size >= 3:
        for a,b,c in zip(input_list[::3],input_list[1::3],input_list[2::3]):
            column_space = '{:<%s}{:<%s}{:<}' % (longest_length, longest_length)
            output = column_space.format(a,b,c)
            print(output)
    elif terminal_space_half >= longest_length and list_size >= 2:
        for a,b in zip(input_list[::2],input_list[1::2]):
            column_space = '{:<%s}{:<}' % longest_length
            output = column_space.format(a,b)
            print(output)

As an explanation this does a few different things.

First it gets the number of columns for the current user's terminal using os.popen.

Second it takes the number of columns and divides in half, increasing to eighth. This will be used to compare the longest string in the list to determine the number of columns best suited for this.

Third is the longest string of the list pulled using the build in python function max().

Forth the length of the longest string is taken and then has one added to it for padding. The length of the list is taken as well so that if the list is less than 8 items it will only list the number of items that exist.

Fifth the longest string length is compared to each of the terminal spaces from one column to eight. If the column is greater than or equal to the length then it can be used. For example is the longest string is 10 and the columns divided by eight(terminal_space_eighth) is 8 but columns divided by seven(terminal_space_seventh) is 12, there will be seven columns. There will be seven because the longest string can fit in 12 characters but not in 8 characters.

It's also worth noting the length of the list is taken into consideration to prevent creating more columns than list items.

Sixth is an expansion of the explanation by @Aman : https://stackoverflow.com/a/1524132/11002603

Indexing Lets let i represent the number determined by terminal size for the sake of this example. input_list[::i] This selects element at i. Adding a number at the front such as input_list[1::i] offsets the starting point(remember python considers 0 a valid number which is why it's not used initially.)

Zipping

Zip is used to create a tuple with elements of a list. For example The output list will look similar to below

zip([string1,string2,string3], [string4,string5, string6], [string7,string8,string9])
output : [(string1,string4,string7), (string2,string5, string8), (string3,string6,string9)]

Using together Depending on the number of columns, the letters are just used to represent a split. So for example if only 5 columns fit in the terminal, the following will be used

for a,b,c,d,e in zip(input_list[::5],input_list[1::5],input_list[2::5], input_list[3::5], input_list[4::5]):

This will take of the tuples created from zipping and store then as a,b,c,d and e variables so we can call them within the loop.

The column space is then used for format each of a,b,c,d and e into respective columns and is where the length of each column is determined. The length is based on the string length determined above.

I needed to adjust each of the columns. I have implemented this code

def print_sorted_list(data, columns):
    if data:
        gap = 2
        ljusts = {}
        for count, item in enumerate(sorted(data), 1):
            column = count % columns
            ljusts[column] = len(item) if (column not in ljusts) else max(ljusts[column], len(item))

        for count, item in enumerate(sorted(data), 1):
            print item.ljust(ljusts[count % columns] + gap),
            if (count % columns == 0) or (count == len(data)):
                print

Example:

foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
           'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel',
           'qgis1.1', 'php_mapscript', 'blablablablablablabla', 'fafafafafafa']
print_sorted_list(foolist, 4)

Output:

blablablablablablabla   exiv2-devel      fafafafafafa    fcgi        
gdal-grass              iconv            mingw-libs      msvcrt      
netcdf                  pdcurses-devel   php_mapscript   qgis-devel  
qgis1.1                 tcltk-demos   

Here is a straightforward way. See the inline comments for explanation:

import shutil
import itertools
from functools import reduce


def split_list(lst, ncols):
    """Split list into rows"""
    return itertools.zip_longest(
        *[lst[i::ncols] for i in range(ncols)], fillvalue=""
    )
    # -- Alternatively --
    # import numpy as np
    # array = np.array(lst)
    # nrows = array.size / ncols + 1
    # return np.array_split(array, int(nrows))


def print_in_columns(lst):
    """Print a list in columns."""
    # Find maximum length of a string in colors_list
    colsize = reduce(lambda x, y: max(x, len(y)), lst, 0)
    # Terminal width
    maxcols = shutil.get_terminal_size()[0]
    ncols = maxcols / (colsize + 1)
    rows = split_list(lst, int(ncols))

    print(
        # Join rows
        "\n".join(
            (
                # Fill items left justified
                " ".join(item.ljust(colsize) for item in row)
                for row in rows
            )
        )
    )

this one prints list in separate columns (order is preserved)

from itertools import zip_longest

def ls(items, n_cols=2, pad=30):
    if len(items) == 0:
        return
    total = len(items)
    chunk_size = total // n_cols
    if chunk_size * n_cols < total:
        chunk_size += 1
    start = range(0, total, chunk_size)
    end = range(chunk_size, total + chunk_size, chunk_size)
    groups = (items[s:e] for s, e in zip(start, end))
    for group in zip_longest(*groups, fillvalue=''):
        template = (' ').join(['%%-%ds' % pad] * len(group))
        print(template % group)

usage:

ls([1, 2, 3, 4, 5, 6, 7], n_cols=3, pad=10)

output:

1          4          7         
2          5                    
3          6                    

note that there may be missing columns if there is not enough number of items, because columns are filled first.

ls([1, 2, 3, 4, 5], n_cols=4)

output:

1          3          5         
2          4    

For Python >=3.6, slight update to @JoshuaZastrow's answer using f-strings and adding clear method to adjust columns

cols = 5
[print(f'{key:20}', end='\t') if (idx + 1) % cols else print(f'{key}') for idx, key in enumerate(list_variable)]

or

cols = 5
for idx, key in enumerate(list_variable):
    if (idx + 1) % cols:
        print(f'{key:20}', end='\t')
    else:
        print(f'{key}')

Here is the short and simple method:

def printList1(list, col, STR_FMT='{}', gap=1):
    list = [STR_FMT.format(x).lstrip() for x in list]
    FMT2 = '%%%ds%%s' % (max(len(x) for x in list)+gap)
    print(''.join([FMT2 % (v, "" if (i+1) % col else "\n") for i, v in enumerate(list)]))

Then here is the better method which can turn off the constant width across the entire list instead optimizing it across the columns, restrict the number of columns to fit the max characters per line or find the optimal number of columns to fit the max characters per line, then force left or right justify, and can still can take the optional format string, and gap width between each column.

def printList2(list, col=None, gap=2, uniform=True, ljust=True, STR_FMT="{}", MAX_CHARS=120, end='\n'):
   list = [STR_FMT.format(x).strip() for x in list]
   Lmax, valid, valid_prev, cp, c = [MAX_CHARS+1], None, None, 1, max(col,1) if col else 1
   LoL_prev, Lmax_prev = [], []

   while True:
       LoL = [list[i::c] for i in range(c)]
       Lmax = [max(len(x)+gap for x in L) for L in LoL]    # Find max width of each column with gap width
       if uniform:                                         # Set each max column width to max across entire set.
           Lmax = [max(Lmax) for m in Lmax]

       valid_prev, valid = valid, sum(Lmax) <= MAX_CHARS

       if (col and (valid or (c == 1))) or not MAX_CHARS:  # If column and valid strlen or MAX_CHARS is empty
           break
       elif valid_prev and not valid_prev == valid:        # If valid_prev exist
           c = cp if valid_prev and not valid else c
           LoL, Lmax = (LoL_prev, Lmax_prev) if valid_prev else (LoL, Lmax)
           break

       LoL_prev, Lmax_prev = LoL, Lmax
       cp, c = c, (c + (+1 if valid else -1))
  
   ljust = '-' if ljust else ''
   FMT = ["%%%s%ds%s" % (ljust, max(Lmax) if uniform else m, end if i+1 == c else '') for i, m in enumerate(Lmax)]
   outStr = ''.join([''.join([f % v for v, f in zip(L, FMT)]) for L in zip(*LoL)])
   remStr = ''.join([f % v for v, f in zip(list[c * (len(list) // c):], FMT)])
   print(outStr+(remStr+end if remStr else remStr), end='')

Testing w/ outputs:

>>> foolist = ['exiv2-devel', 'mingw-libs', 'tcltk-demos', 'fcgi', 'netcdf',
             'pdcurses-devel', 'msvcrt', 'gdal-grass', 'iconv', 'qgis-devel', 
             'qgis1.1', 'php_mapscript']
>>> printList2(foolist)
exiv2-devel     mingw-libs      tcltk-demos     fcgi            netcdf          pdcurses-devel  msvcrt          
gdal-grass      iconv           qgis-devel      qgis1.1         php_mapscript   

>>> printList2(foolist, MAX_CHARS=48, uniform=False, gap=3)
exiv2-devel   mingw-libs   tcltk-demos      
fcgi          netcdf       pdcurses-devel   
msvcrt        gdal-grass   iconv            
qgis-devel    qgis1.1      php_mapscript    

>>> printList2(foolist, col=2, MAX_CHARS=48, uniform=False, gap=3)
exiv2-devel   mingw-libs       
tcltk-demos   fcgi             
netcdf        pdcurses-devel   
msvcrt        gdal-grass       
iconv         qgis-devel       
qgis1.1       php_mapscript    

>>> printList2(foolist, col=2, MAX_CHARS=48, uniform=False, ljust=False, gap=2)
  exiv2-devel      mingw-libs
  tcltk-demos            fcgi
       netcdf  pdcurses-devel
       msvcrt      gdal-grass
        iconv      qgis-devel
      qgis1.1   php_mapscript

>>> printList2(foolist, col=10, MAX_CHARS=48, uniform=True, ljust=False, gap=2)
     exiv2-devel      mingw-libs     tcltk-demos
            fcgi          netcdf  pdcurses-devel
          msvcrt      gdal-grass           iconv
      qgis-devel         qgis1.1   php_mapscript

>>> from math import pi
>>> FloatList = [pi**(i+1) for i in range(32)]
>>> printList2(FloatList, STR_FMT="{:.5g},", col=7, ljust=False)
      3.1416,      9.8696,      31.006,      97.409,      306.02,      961.39,      3020.3,
      9488.5,       29809,       93648,   2.942e+05,  9.2427e+05,  2.9037e+06,  9.1222e+06,
  2.8658e+07,  9.0032e+07,  2.8284e+08,  8.8858e+08,  2.7916e+09,    8.77e+09,  2.7552e+10,
  8.6556e+10,  2.7192e+11,  8.5427e+11,  2.6838e+12,  8.4313e+12,  2.6488e+13,  8.3214e+13,
  2.6142e+14,  8.2129e+14,  2.5802e+15,  8.1058e+15,

For Python3, I used python - How do you split a list into evenly sized chunks? - Stack Overflow to create

def chunkSectionList(listToPrint, columns):
    """ separate a list into chunks of n-items """
    for i in range(0, len(listToPrint), columns):
        yield listToPrint[i:i + columns]


def printSections(listToPrint, columns):
    remainder = len(listToPrint) % columns
    listToPrint += (columns - remainder) * \
        (" ") if remainder != 0 else listToPrint
    for sectionsLine in chunkSectionList(listToPrint, columns):
        formatStr = columns * '{:<30}'
        print(formatStr.format(* sectionsLine))
Related