sds
sds

Reputation: 60044

Can I define my own format specification?

I would like to define my own str.format() specification, e.g.

def earmuffs(x):
    return "*"+str(x)+"*"

to be used, e.g., like this:

def triple2str(triple, fmt="g"):
    return "[{first:{fmt}} & {second:+{fmt}} | {third}]".format(
        first=triple[0], second=triple[1], third=triple[2], fmt=fmt)

so that:

## this works:
>>> triple2str((1,-2,3))
'[1 & -2 | 3]'
>>> triple2str((10000,200000,"z"),fmt=",d")
'[10,000 & +200,000 | z]'

## this does NOT work (I get `ValueError: Invalid conversion specification`)
>>> triple2str(("a","b","z"),fmt=earmuffs)
'[*a* & *b* | z]'

The best I could come up with so far is

def triple2str(triple, fmt=str):
    return "[{first} & {second} | {third}]".format(
        first=fmt(triple[0]), second=fmt(triple[1]), third=triple[2])

which works like this:

>>> triple2str((1,-2,3))
'[1 & -2 | 3]'
>>> triple2str((10000,200000,"z"),fmt="{:,d}".format)
'[10,000 & 200,000 | z]' # no `+` before `2`!
>>> triple2str((10000,200000,"z"),fmt=earmuffs)
'[*10000* & *200000* | z]'

Is this really the best I can do? What I am unhappy about is that that it is unclear how to incorporate the modifiers (e.g., + above).

Is str.format extensible?

Upvotes: 0

Views: 362

Answers (2)

s3bw
s3bw

Reputation: 3049

In python 3 you can call functions in f-strings, perhaps this can help.

def earmuffs(val):
    return "*{}*".format(val)

form = lambda a: f"method {earmuffs(a[0])} and method {earmuffs(a[1])}"

b = ('one', 'two')

form(b)
>>>'method *one* and method *two*'

Upvotes: 1

Sergey Vasilyev
Sergey Vasilyev

Reputation: 4189

str.format itself is not extensible. However, there are two ways:


1.

Use your custom string formatter: https://docs.python.org/2/library/string.html#custom-string-formatting

Override the format_field(obj, format_spec) method to catch the callable format_spec. Then call your formatter directly.

This code snippet can help you (it works with py 3.5 & 2.7 at least):

import string

class MyFormatter(string.Formatter):
    def __init__(self, *args, **kwargs):
        super(MyFormatter, self).__init__(*args, **kwargs)
        self.fns = {}

    def format_field(self, value, format_spec):
        # print(repr(value), repr(format_spec))

        # intercept {fmt} part with functions:
        if callable(value):
            result = super(MyFormatter, self).format_field(value, format_spec)
            self.fns[result] = value
            return result

        # intercept {var:{fmt}} with remembered {fmt}:
        if format_spec in self.fns:
            return self.fns[format_spec](value)
        else:
            return super(MyFormatter, self).format_field(value, format_spec)


def fn(val):
    return '*{}*'.format(val)


f = MyFormatter()
print(f.format("{var:{fmt}}", var=1000, fmt='g'))
print(f.format("{var:{fmt}}", var=1000, fmt=fn))

2.

Define per-object format method __format__(self, format_spec), where format_spec is whatever goes after : in e.g. {var:g}. You can format self-presentation of the object as you wish.

However, in your case, the objects are ints/strs, not the custom objects, so this method will also not help much.


As the conclusion:

Yes, your solution in the question is sufficient and probably the simplest one.

Upvotes: 2

Related Questions