19

I read that I can use Python's Format Specification Mini-Language to have more control over how strings are displayed. However, I am having a hard time figuring out how to use it to display floats aligned on the decimal point.

For example, say I have the following three lists:

job_IDs = ['13453', '123', '563456'];
memory_used = [30, 150.54, 20.6];
memory_units = ['MB', 'GB', 'MB'];

I would like to iterate through these three lists and print

Job 13453:   30      MB
Job 123:    150.54   MB
Job 563456:  20.6    GB

So far I have tried:

for i in range(len(jobIDs)):
    my_str = "{item:15}{value:6} {units:3}".format( 
    item='Job ' + job_IDs[i] + ':' , value=str(memories[i]),units=memory_units[i]);

    print my_str

which prints:

Job 13453:   30      MB
Job 123:     150.54  MB
Job 563456:  20.6    GB

which is almost right, but it does not align the floats around the decimal point. How can I use Python's Format Specification Mini-Language to do it the way I need?

bad_coder
  • 11,289
  • 20
  • 44
  • 72
Amelio Vazquez-Reina
  • 91,494
  • 132
  • 359
  • 564
  • Your `memory_used` list is a list of strings. Typically when you want to print floats in a formatted way, you'll use floats. What you have now is a set of strings, and you want the formatting to understand that it should be treated specially. But maybe that's just in the code you posted here? – Mattias Nilsson Mar 03 '12 at 19:27
  • Thanks @MattiasNilsson I have updated my code accordingly. – Amelio Vazquez-Reina Mar 03 '12 at 19:37

4 Answers4

14

This is what you want:

for i in range(len(job_IDs)):
    print "Job {item:15} {value[0]:>6}.{value[1]:<6} {units:3}".format(item=job_IDs[i]+':', value=memory_used[i].split('.') if '.' in memory_used[i] else (memory_used[i], '0'), units=memory_units[i])

Here is how it works:

This is the main part: value=memory_used[i].split('.') if '.' in memory_used[i] else (memory_used[i], '0'), which means: if there is a decimal point, split the string as the whole and decimal part, or set the decimal part to 0.

Then in the format string: {value[0]:>6}.{value[1]:<6} means, the whole part shifted right, followed by a dot, then the decimal part shifted left.

which prints:

Job 13453:              30.0      MB
Job 123:               150.54     GB
Job 563456:             20.6      MB
quantum
  • 3,672
  • 29
  • 51
  • Thanks @xiaomao. I have never seen an if statement where the command to be executed comes **before** the condition. Do you have any pointers where I can read more about this? Also, what is the role of number `6` in the format string `value[0]:<6`? – Amelio Vazquez-Reina Mar 03 '12 at 20:44
  • 1
    @roseck python's ?:-style syntax is `(true) if (condition) else (false)`. The `<` in `<6` is shift left and `6` is pad to 6 characters. – quantum Mar 03 '12 at 21:01
  • 1
    -1: Actually this doesn't work if `memory_used` is a list of integers and floats as shown in the OP's question. If it is, a `TypeError: argument of type 'int' is not iterable` occurs. – martineau Jun 20 '15 at 10:08
12

Here's another implementation based on .split('.') idea. It might be more readable. Split on '.', right-align the left part, left-align the right part:

width = max(map(len, job_IDs)) # width of "job id" field 
for jid, mem, unit in zip(job_IDs, memory_used, memory_units):
  print("Job {jid:{width}}: {part[0]:>3}{part[1]:1}{part[2]:<3} {unit:3}".format(
    jid=jid, width=width, part=str(mem).partition('.'), unit=unit))

Output

Job 13453 :  30     MB 
Job 123   : 150.54  GB 
Job 563456:  20.6   MB 
jfs
  • 399,953
  • 195
  • 994
  • 1,670
4

In case it helps, here's a similar function I use:

def align_decimal(number, left_pad=7, precision=2):
    """Format a number in a way that will align decimal points."""
    outer = '{0:>%i}.{1:<%i}' % (left_pad, precision)
    inner = '{:.%if}' % (precision,)
    return outer.format(*(inner.format(number).split('.')))

It allows a fixed precision after the decimal point.

mstringer
  • 2,242
  • 3
  • 25
  • 36
1

This does it:

import re

job_IDs = ['13453', '123', '563456']
memory_used = ['30', '150.54', '20.6']
memory_units = ['MB', 'GB', 'MB']

for i in range(len(job_IDs)):
    lh=re.match(r'(\d+)',memory_used[i]).group(1)
    if '.' in memory_used[i]:
        rh=re.match(r'(\d+)\.(\d+)',memory_used[i]).group(2)
        sep='.'
    else:
        rh=''    
        sep=' '


    my_str = "{item:15}{l:>6}{s:1}{r:6} {units:3}".format( 
    item='Job ' + job_IDs[i] + ':' , l=lh,s=sep,r=rh,units=memory_units[i])

    print my_str

To get your precise out put, you need to break the strings on the optional '.'

You could also convert those strings to floats.

dawg
  • 98,345
  • 23
  • 131
  • 206