3

In order to print a header for tabular data, I'd like to use only one format string line and one spec for column widths w1, w2, w3 (or even w = x, y, z if possible.)

I've looked at this but tabulate etc. don't let me justify things in the column like format does.

This approach works:

head = 'eggs', 'bacon', 'spam'  
w1, w2, w3 = 8, 7, 10  # column widths  
line = '  {:{ul}>{w1}}  {:{ul}>{w2}}  {:{ul}>{w3}}'  
under = 3 * '='  
print line.format(*head, ul='', w1=w1, w2=w2, w3=w3)  
print line.format(*under, ul='=', w1=w1, w2=w2, w3=w3)  

Must I have individual names as widths {w1}, {w2}, ... in the format string? Attempts like {w[1]}, {w[2]}, give either KeyError or keyword can't be an expression.

Also I think the w1=w1, w2=w2, w3=w3 is not very succinct. Is there a better way?

Community
  • 1
  • 1
RolfBly
  • 3,612
  • 5
  • 32
  • 46
  • 2
    Why not use a dictionary - `w = {'w1': 8, 'w2': 7, 'w3': 10}` then call `line.format(..., **w)`. You could even build the dictionary dynamically from `[8, 7, 10]` - `w = {'w{}'.format(index): value for index, value in enumerate([8, 7, 10], 1)}`. – jonrsharpe Jul 02 '16 at 10:48
  • @jonrsharpe Thank you! I'll admit I had to study your automatic dict filler for a bit before I understood. See below also.. – RolfBly Jul 02 '16 at 21:21

3 Answers3

5

Using the f-string format becomes very easy nowadays.

If you were using

print(f'{token:10}')

And you want the 10 to be another variable (for example the max length of all the tokens), you would write

print(f'{token:{maxTokenLength}}')

In other words, enclose the variable within {}


In your particular case, all you need is this.

head = 'eggs', 'bacon', 'spam'  
w1, w2, w3 = 8, 7, 10  # column widths  

print(f'  {head[0]:>{w1}}  {head[1]:>{w2}}  {head[2]:>{w3}}')
print(f'  {"="*w1:>{w1}}  {"="*w2:>{w2}}  {"="*w3:>{w3}}')

Which produces

      eggs    bacon        spam
  ========  =======  ==========
Rub
  • 2,071
  • 21
  • 37
1

Specifying w[0], w[1], w[2] should work if you defined w = 8, 7, 10 and passed w as keyword argument like below:

>>> head = 'eggs', 'bacon', 'spam'
>>> w = 8, 7, 10  # <--- list is also okay
>>> line = '  {:{ul}>{w[0]}}  {:{ul}>{w[1]}}  {:{ul}>{w[2]}}'
>>> under = 3 * '='
>>> print line.format(*head, ul='', w=w)  # <-- pass as a keyword argument
      eggs    bacon        spam
>>> print line.format(*under, ul='=', w=w)  # <-- pass as a keyword argument
  ========  =======  ==========
falsetru
  • 357,413
  • 63
  • 732
  • 636
1

This is jonrsharpe's comment to my OP, worked out so as to visualise what's going on.

line = '  {:{ul}>{w1}}  {:{ul}>{w2}}  {:{ul}>{w3}}'
under = 3 * '_'

head = 'sausage', 'rat', 'strawberry tart'

# manual dict 
v = {'w1': 8, 'w2':5, 'w3': 17}
print line.format(*under, ul='_', **v)

# auto dict
widthl = [8, 7, 9]
x = {'w{}'.format(index): value for index, value in enumerate(widthl, 1)}
print line.format(*under, ul='_', **x)     

The point is that I want to be able to quickly rearrange the header without having to tweak the format string. The auto dict meets that requirement very nicely.

As for filling a dict in this way: WOW!

RolfBly
  • 3,612
  • 5
  • 32
  • 46