1

Same question as this, but for python. If I have a list

['a','b','cdefg','jk','lmnopqr','st','uv','wxyz',1,2,3,4]

I would like to print it something like the default output of the ls command in bash, something like

[ 'a',   'b',        'cdefg',
  'jk',  'st',       'lm',
  'no',  'pqrstuv',  'wxyz',  
  1,     2,          3,
  4,     ]

The goal being maximization of visually parsable data on the screen at one time. I can only find the pprint library, which for simple lists either prints as default or one per line. Does something like this exist already?

Community
  • 1
  • 1
hawkjo
  • 400
  • 2
  • 11
  • 1
    Your example does not match that of the linked question. In the output of `ls`, consecutive elements are in the same column, not in the same row. – José Tomás Tocino Jul 29 '14 at 23:43
  • True, but that was intentional. I think it would be confusing if default output wrote them consecutively horizontally and then pretty print made it vertical. I don't have terribly strong feelings one way or the other, though. – hawkjo Jul 30 '14 at 00:01
  • As a side note, there's been some recent interest in the Python community behind beefing up `pprint`, so if you have ideas for pretty-printing—like equal-sized columns—now might be a great time to bring them up. Start by searching http://bugs.python.org and the python-ideas and -dev [mailing lists](https://mail.python.org/mailman/listinfo/) to see where things stand and figure out where to speak up. – abarnert Jul 30 '14 at 00:17

3 Answers3

4
def columnify(iterable):
    # First convert everything to its repr
    strings = [repr(x) for x in iterable]
    # Now pad all the strings to match the widest
    widest = max(len(x) for x in strings)
    padded = [x.ljust(widest) for x in strings]
    return padded

Now you should be able to use pprint.pprint(compact=True), or textwrap, or other tools to get the formatting however you want.

But if you want to do it manually, it's not too hard to do anything you want. For example:

def colprint(iterable, width=72):
    columns = columnify(iterable)
    colwidth = len(columns[0])+2
    perline = (width-4) // colwidth
    print('[ ', end='')
    for i, column in enumerate(columns):
        print(column, end=', ')
        if i % perline == perline-1:
            print('\n  ', end='')
    print(' ]')

So:

>>> arr = ['a', 'b', 'cdefg', 'jk', 'lmnopqr', 'st', 'uv', 'wxyz', 1, 2, 3, 4]
>>> colprint(arr, 60)
[ 'a'      , 'b'      , 'cdefg'  , 'jk'     , 'lmnopqr',
  'st'     , 'uv'     , 'wxyz'   , 1        , 2        ,
  3        , 4        ,  ]

This still won't give you exactly what ls does; for example, ls has some heuristics that try to make sure that filenames that are "too long" don't count toward the max width, and instead span multiple columns. If you really want to do everything exactly the same as ls, you probably need to look at the source code and translate from C to Python…

Also, take a look at pprint. Whenever a module's documentation starts off with a link to the source code, that means the module was meant to serve as useful sample code, as well as to be useful on its own. So, if you want to look at the rules it uses to determine when to split lines (based on the compact flag as well as the widths), you should be able to figure it out from there.

abarnert
  • 354,177
  • 51
  • 601
  • 671
4

Thank you everyone for your thoughts and for clarifying that there indeed is no extant solution for this. I coded up exactly what I wanted, code below. In a terminal with a width of 80 characters

pprint_list( [ string.lowercase[i % 26]*random.randint(1,20) for i in range(8) ] )

printed as

[ 'aaaa',         'bbbbbbbbbbbbbbb',   'ccccccccccccccccccc',  'd',  
  'eeeeeeeeeee',  'ffffffffffffffff',  'gggggggggggggggg',     'h'   ]

A look at the code shows there is obviously some overhead figuring out how to size the columns, with more entries and wider terminals requiring more calculation, but moving the number of entries up from 8 to 50000 didn't have much lag time, so I think I'm happy with it.

This code relies on the get_terminal_size function here.

#!/usr/bin/env python
def pprint_list(input_list):
    (term_width, term_height) = get_terminal_size()
    if len( str(input_list) ) <= term_width:
        print input_list
        return

    repr_list = [repr(x) for x in input_list]
    min_chars_between = 3 # a comma and two spaces
    usable_term_width = term_width - 3 # For '[ ' and ']' at beginning and end
    min_element_width = min( len(x) for x in repr_list ) + min_chars_between
    max_element_width = max( len(x) for x in repr_list ) + min_chars_between
    if max_element_width >= usable_term_width:
        ncol = 1
        col_widths = [1]
    else:
        # Start with max possible number of columns and reduce until it fits
        ncol = min( len(repr_list), usable_term_width / min_element_width  )
        while True:
            col_widths = [ max( len(x) + min_chars_between \
                                for j, x in enumerate( repr_list ) if j % ncol == i ) \
                                for i in range(ncol) ]
            if sum( col_widths ) <= usable_term_width: break
            else: ncol -= 1

    sys.stdout.write('[ ')
    for i, x in enumerate(repr_list):
        if i != len(repr_list)-1: x += ','
        sys.stdout.write( x.ljust( col_widths[ i % ncol ] ) )
        if i == len(repr_list) - 1:
            sys.stdout.write(']\n')
        elif (i+1) % ncol == 0:
            sys.stdout.write('\n  ')
hawkjo
  • 400
  • 2
  • 11
  • A generalization of the problem of formatting text into columns is described in [this answer](http://stackoverflow.com/questions/12503655/how-do-i-print-parameters-of-multiple-python-objects-in-table-form/12503806#12503806) to the question [_How do I print parameters of multiple Python objects in table form?_](http://stackoverflow.com/questions/12503655/how-do-i-print-parameters-of-multiple-python-objects-in-table-form) – martineau Jul 30 '14 at 02:26
  • Thanks for this, what I was looking for, I don't think what martineau linked can do the same thing. – Mathieu_Du Nov 28 '15 at 00:32
0

I have created a module columnify.py to achieve the same goal.

Given the following list as the input:

items: list[str] = [
    "Canidae", "Felidae", "Cat", "Cattle", "Dog",
    "Donkey", "Goat", "Guinea pig", "Horse", "Pig",
    "Rabbit", "Fancy rat varieties", "laboratory rat strains",
]

The following results can be generated:

Default

Canidae  Cat     Dog     Goat        Horse  Rabbit               laboratory rat strains
Felidae  Cattle  Donkey  Guinea pig  Pig    Fancy rat varieties

Horizon first

Canidae     Felidae  Cat  Cattle  Dog                  Donkey                  Goat
Guinea pig  Horse    Pig  Rabbit  Fancy rat varieties  laboratory rat strains

center()

Canidae   Cat     Dog       Goat     Horse         Rabbit        laboratory rat strains
Felidae  Cattle  Donkey  Guinea pig   Pig   Fancy rat varieties

rjust()

Canidae     Cat     Dog        Goat  Horse               Rabbit  laboratory rat strains
Felidae  Cattle  Donkey  Guinea pig    Pig  Fancy rat varieties

Custom delimiter ( | )

Canidae | Cattle | Goat       | Pig                 | laboratory rat strains
Felidae | Dog    | Guinea pig | Rabbit
Cat     | Donkey | Horse      | Fancy rat varieties

Indent (4 spaces):

    Canidae  Cattle  Goat        Pig                  laboratory rat strains
    Felidae  Dog     Guinea pig  Rabbit
    Cat      Donkey  Horse       Fancy rat varieties
Johann Chang
  • 1,281
  • 2
  • 14
  • 25