string formatting with mapping & '*'... is this a bug?

Bengt Richter bokr at oz.net
Fri Sep 10 01:46:23 EDT 2004


On Fri, 10 Sep 2004 00:06:43 -0400, Pierre Fortin <pfortin at pfortin.com> wrote:

>On 10 Sep 2004 00:42:19 GMT Bengt wrote:
>
>> >I was hoping to use the likes of:  "%(key)*.*f" % map
>> >however, unlike with the non-(key) formats, there appears to be no way
>> >to specify a "*.*" size when a map is used...
>> >
>> Well, you could subclass str and override its __mod__ method ;-)
>> If you had class format(str): ..., you could then write
>>     format('%(key)*.*f") % map
>> and have it do whatever you like.
>
>This must be one of those days when my density goes up.... :^/
>
>Can you fill in the blanks on this...?  Mismatched quotes aside, I just
Oops on the quotes ;-/

>don't seem to grok how map's key gets in there since there is a ")"
>between the format string and "%" -- then, I still don't see how "*.*"
>gets set as it would in: "%*.*f" % (width,max,float)
>
>I'll try to finally get a handle on subclassing/overriding with this.... 
>:>
I was making a quick comment that if you really wanted to you could
intercept all the arguments and interpret them as you wish. The line I wrote
would demand some conventions about what was in the map, or indeed it could
be a custom mapping object. To see the arguments, we can do (starting with
your original post example mapping):

 >>> mapping = {'one':1,'two':2}
 >>> "%(two)d" % mapping
 '2'
 >>> "%(two)6d" % mapping
 '     2'

And you showed that:
 >>> "%(two)*d" % (mapping,6)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: format requires a mapping

Let's see:

 >>> class format(str):
 ...     def __mod__(self, *a, **kw): print repr(self), a, kw
 ...

 >>> format("%(two)*d") % mapping
 '%(two)*d' ({'two': 2, 'one': 1},) {}

But we need an argument for the '*':
 >>> format("%(two)*d") % (mapping,6)
 '%(two)*d' (({'two': 2, 'one': 1}, 6),) {}


Ok, we see that we have the custom string back as self, and a is the args tuple
for the rhs of the % operator, and there are no keyword args.

But we need a way to pick up the '*' values. I think the most straightforward way
would be to have a mapping argument be the first, for %(name)...x lookups, and
optional additional arguments like an ordinary tuple, like

 >>> format('%(key)*.*f') % ({'key':2.3}, 7, 3)
 '%(key)*.*f' (({'key': 2.2999999999999998}, 7, 3),) {}

The intent being like
 >>> '%7.3f'%2.3
 '  2.300'

You could go hog wild and allow multiple mapped values in a single
format, as in this contrived combination:

 >>> format('%(key)*.("three")f') % ({'key':2.3, 'three':3}, 7)
 '%(key)*.("three")f' (({'three': 3, 'key': 2.2999999999999998}, 7),) {}

BTW, note that a mapping only has to support __getitem__, so you can make a
custom mapping to synthesize interesting named things or find them in interesting
places, and even have special methods that could be called from format.__mod__ for
whatever you think up. Just so your end product is somewhat explainable.

This contrivance illustrates custom mapping as a one-liner class instance
returning the map key uppercased instead of looking something up.

 >>> '%(hi)s %(ho)8s' % type('',(),{'__getitem__':lambda s,x: x.upper()})()
 'HI       HO'

It's more readable if you define a class as usual ;-)
 >>> class U(object):
 ...     def __getitem__(self, key): return key.upper()
 ...
 >>> umap = U()
 >>> '%(hi)s %(ho)8s' % umap
 'HI       HO'

Custom stuff tends to be slower than builtins written in C, of course.
I'll leave the rest as an exercise from here ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list