HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Right-justifying a number with dot leaders

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
numberwithdotjustifyingleadersright

Problem

I'm working on a small library to help me format numbers easier as my current project deals with a lot of them. I want to be able to just supply the value, precision, width, and fillchar to a method and have it do all the work for me.

Based on the standard way of doing things with rjust()

print '{:.2f}'.format(123.456).rjust(10, '.')
# ....123.46


… I could write:

def float_rjust(value, width, precision=2, fillchar=' ', separator='', sign=''):
    #[sign][#][0][width][,][.precision][type]
    spec = '{{:{1}{2}{3}.{4}f}}'.format(
        sign,
        width,
        separator,
        precision
    )
    return spec.format(value).rjust(width, fillchar)


However, I have found that the format() method actually allows for justifications in it:

print '{:.>10.2f}'.format(123.456)
# ....123.46


… so I could also write it this way:

def float_rjust(value, width, precision=2, fillchar=' ', separator='', sign=''):
    #[[fill]align][sign][#][0][width][,][.precision][type]
    spec = '{{:{0}>{1}{2}{3}.{4}f}}'.format(
        fillchar,
        sign,
        width,
        separator,
        precision
    )
    return spec.format(value)


My main question is, should I used the [[fill]align] formatting spec in format() to do justification, or is it more Pythonic to use format().rjust()?

Solution

In terms of efficiency, the second form is probably slightly better, as it creates fewer string objects:

def float_rjust(value, width, precision=2, fillchar=' ', separator='', sign=''):
    #[sign][#][0][width][,][.precision][type]
    spec = '{{:{1}{2}{3}.{4}f}}'.format(  # 1
        sign,
        width,
        separator,
        precision
    )  # 2
    value = spec.format(value)  # 3 - slight refactor for clarity
    return value.rjust(width, fillchar)  # 4


vs.

def float_rjust(value, width, precision=2, fillchar=' ', separator='', sign=''):
    #[[fill]align][sign][#][0][width][,][.precision][type]
    spec = '{{:{0}>{1}{2}{3}.{4}f}}'.format(  # 1
        fillchar,
        sign,
        width,
        separator,
        precision
    )  # 2
    return spec.format(value)  # 3


In general terms, I would use e.g. rjust only where that was the only formatting I required; it seems awkward to split the formatting into two steps.

However, I would probably rearrange this completely. You have combined two steps that I think should probably be separate: creating a specification, and applying it to an object.

Using format(value, spec) rather than spec.format(value) (i.e. the format function vs. the str.format method) lets you simplify the spec (getting rid of the extra braces):

def float_spec(width, precision=2, fillchar=' ', separator='', sign=''):
    """Create a specification for formatting floats."""
    return '{}>{}{}{}.{}f'.format(fillchar, sign, width, separator, precision)


You can now inline the format call:

>>> print format(123.456, float_spec(width=10, fillchar='.'))
....123.46


For greater efficiency, as float_spec would return equal (but not identical) strings for the same inputs, you could add memoization/caching. You could also add validation (of either the parameters or created specs) to ensure that it outputs a valid specification for format.

Code Snippets

def float_rjust(value, width, precision=2, fillchar=' ', separator='', sign=''):
    #[sign][#][0][width][,][.precision][type]
    spec = '{{:{1}{2}{3}.{4}f}}'.format(  # 1
        sign,
        width,
        separator,
        precision
    )  # 2
    value = spec.format(value)  # 3 - slight refactor for clarity
    return value.rjust(width, fillchar)  # 4
def float_rjust(value, width, precision=2, fillchar=' ', separator='', sign=''):
    #[[fill]align][sign][#][0][width][,][.precision][type]
    spec = '{{:{0}>{1}{2}{3}.{4}f}}'.format(  # 1
        fillchar,
        sign,
        width,
        separator,
        precision
    )  # 2
    return spec.format(value)  # 3
def float_spec(width, precision=2, fillchar=' ', separator='', sign=''):
    """Create a specification for formatting floats."""
    return '{}>{}{}{}.{}f'.format(fillchar, sign, width, separator, precision)
>>> print format(123.456, float_spec(width=10, fillchar='.'))
....123.46

Context

StackExchange Code Review Q#93190, answer score: 4

Revisions (0)

No revisions yet.