[Python-3000] More PEP 3101 changes incoming

Ron Adam rrr at ronadam.com
Wed Aug 15 22:52:56 CEST 2007



Jim Jewett wrote:
> On 8/14/07, Ron Adam <rrr at ronadam.com> wrote:
>> Jim Jewett wrote:
>>> On 8/13/07, Ron Adam <rrr at ronadam.com> wrote:
> 
>>>>        {name[:][type][alignment_term][,content_modifying_term]}
> 
>>> ...  You used the (alignment term)
>>> width as the number of digits before the decimal,
>>> instead of as the field width.
> 
>> You can leave out either term.  So that may have
>> been what you are seeing.
> 
> I thought I was going based on your spec, rather than the examples.
> 
>>      {0:f8.2}
>
> Because there is no comma, this should be a field of width 8 -- two
> after a decimal point, the decimal point itself, and at most 5 digits
> (or 4 and sign) before the the decimal point
> 
> I (mis?)read your messge as special-casing float to 8 digits before
> the decimal point, for a total field width of 11.

No, but I see where the confusion is coming from.  The spec is split up a 
bit different than what you are expecting.

Maybe this will help...

       {name[:[type][align][,format]]}

If the alignment is left out, the comma isn't needed so it becomes..

       {name[:[type][fomrat]}

Which is what you are seeing.


BUT, after trying a sequential specifications terms... and finding it works 
better this changes a bit.. more on this at the end of this message.  I'll 
try to answer your questions first.

> ...
>> Minimal width with fill for shorter than width items.  It
>> expands if the length of the item is longer than width.
>  ...
>>> Can I say "width=whatever it takes, up to 72 chars ...
>>> but don't pad it if you don't need to"?
> 
>> That's the default behavior.
> 
> If width is only a minimum, where did it get the maximum of 72 chars part?

Ok.. we aren't communicating...  Lets see if I can clear this up.

       width = whatever it takes    # size of a field
       max_width = 72

Padding doesn't make sense here.  Whatever it takes is basically saying 
there is no minimum width, so there is nothing to say how much padding to 
use.  Unless you want a fixed 72 width with padding.  Then the whatever it 
takes part doesn't makes sense.

So if you want a padded width of 30, and a maximum width of 72, you would 
use the following.

     {0:s^30/_,+72}    Pad strings shorter than 30 with '_',
                       trim long strings to 72 max.

In this case the +72 is evaluated in the context of a string.

>>> I'm not sure that variable lengths and alignment even
>>> *should* be supported in the same expression, but if
>>> forcing everything to fixed-width would be enough of
>>> a change that it needs an explicit callout.
> 
>> Alignment is needed for when the length of the value is shorter than the
>> length of the field.  So if a field has a minimal width, and a value is
>> shorter than that, it will be used.
> 
> I should have been more explicit -- variable length *field*.  How can
> one report say "use up to ten characters if you need them, but only
> use three if that is all you need" and another report say "use exactly
> ten characters; right align and fill with spaces."
> 
> -jJ

    {0:s<3,+10}     The second part is like s[:+10]

Minimum left aligned of 3, (strings of 1 and 2 go to the left with spaces 
as padding up to 3 characters total, but expand up to 10.  Anything over 
that is trimmed to 10 characters.

    {0:s>10,+10}

Right align shorter than 10 character strings and pad with spaces, and cut 
longer strings to 10 characters from the left.

    {0:s>10,-10}   The second term is like s[-10:]

Right align shorter than 10 character strings, and pad with spaces, and cut 
longer strings to 10 characters from the right.




Now... For different variation.

I've found we can do all this by chaining multiple specifiers in a simple 
left to right evaluated expression.  It doesn't split the type letters from 
the terms like what confused you earlier, so you may like this better.

The biggest problem with this is the use of commas for separators, they can 
clash with commas in the specifiers. This is a problem with any multi part 
split specifier as well, so if you can think of a way to resolve that, it 
would be good.



BTW, The examples above work unchanged.

      {0:s>10,+10}

This is equivalent to...

       s>10,s+10

We can drop the 's' off the second term because the result of the first 
term is a string.  Or it can be left on for clarity.  So this evaluates to...

       value.__format__('s>10').__format__('+10')

We could combine the terms, but because the behavior is so different, 
aligning  vs clipping, I don't think that's a good idea even though they 
are both handled by the string type.

With numbers it works the same way...

      {0:f10.2,>12}

      value.__format__('f10.2').__format__('>12')

Here a string is returned after the f format spec is applied, and then 
string's __format__ handles the field alignment.  The leading 's' isn't 
needed for 's>12' because the type is already a string.


It's much simpler than it may sound in practice.  I also have a test (proof 
of concept) implementation if you'd like to see it.

I've worked into parts of Talons latest PEP update as well.  There are 
still plenty of things to add to it though like scientific notation and 
general number forms.  Unused argument testing, nested terms, and attribute 
access.


     General form:

         {label[:spec1[,spec2][,spec3]...]}


     Each values.__format__ method is called with the next specifier
     from left to right sequence.

     In most cases only one or two specifiers are needed.


     * = Not implemented yet

     The general string presentation type.

         's' - String. Outputs aligned and or trimmed strings.
         'r' - Repr string. Outputs a string by using repr().

          Strings are the default for anything without a __format__ method.


     The available integer presentation types are:

       * 'b' - Binary. Outputs the number in base 2.
       * 'c' - Character. Converts the integer to the corresponding
               unicode character before printing.
         'd' - Decimal Integer. Outputs the number in base 10.
         'o' - Octal format. Outputs the number in base 8.
         'x' - Hex format. Outputs the number in base 16, using lower-
               case letters for the digits above 9.
         'X' - Hex format. Outputs the number in base 16, using upper-
               case letters for the digits above 9.

     The available floating point presentation types are:

       * 'e' - Exponent notation. Prints the number in scientific
               notation using the letter 'e' to indicate the exponent.
       * 'E' - Exponent notation. Same as 'e' except it uses an upper
               case 'E' as the separator character.
         'f' - Fixed point. Displays the number as a fixed-point
               number.
         'F' - Fixed point. Same as 'f'.
       * 'g' - General format. This prints the number as a fixed-point
               number, unless the number is too large, in which case
               it switches to 'e' exponent notation.
       * 'G' - General format. Same as 'g' except switches to 'E'
               if the number gets to large.
       * 'n' - Number. This is the same as 'g', except that it uses the
               current locale setting to insert the appropriate
               number separator characters.
         '%' - Percentage. Multiplies the number by 100 and displays
               in fixed ('f') format, followed by a percent sign.


     String alignment:

         Sets minimum field width, justification, and padding chars.

         [s|r][justify][width][/padding_char]

         <   left justify            (default)
         >   right justify
         ^   center

         width               minimum field width

         /padding_char       A single character


     String clipping:

         [s|r][trimmed width]

         +n  Use n characters from beginning.  s[:+n]
         -n  use n characters form end. s[-n:]


     Numeric formats:

         [type][sign][0][digits][.decimals][%]

         signs:
             -   show negative sign   (defualt)
             +   show both negative and positive sign
             (   parentheses around negatives, spaces around positives
                   (ending ')' is optional.)

         %   muliplies number by 100 and places ' %' after it.

         0   pad digits with leading zeros.

         digits      number of digits before the decimal

         decimals    number of digits after the decimal


EXAMPLES:

     >>> short_str = fstr("World")
     >>> long_str = fstr("World" * 3)
     >>> pos_int = fint(12345)
     >>> neg_int = fint(-12345)
     >>> pos_float = ffloat(123.45678)
     >>> neg_float = ffloat(-123.45678)


     >>> fstr("Hello {0}").format(short_str)    # default __str__
     'Hello World'

     >>> fstr("Hello {0:s}").format(short_str)
     'Hello World'

     >>> fstr("Hello {0:r}").format(short_str)
     "Hello 'World'"

     >>> fstr("Hello {0:s<10}").format(short_str)
     'Hello World     '

     >>> fstr("Hello {0:s^10/_}").format(short_str)
     'Hello __World___'

     >>> fstr("Hello {0:s+12,<10}").format(short_str)
     'Hello World     '

     >>> fstr("Hello {0:s+12,<10}").format(long_str)
     'Hello WorldWorldWo'

     >>> fstr("Hello {0:r+12,<10}").format(long_str)
     "Hello 'WorldWorldW"

     >>> fstr("Hello {0:s-12}").format(long_str)
     'Hello ldWorldWorld'


     INTEGERS:

     >>> fstr("Item Number: {0}").format(pos_int)
     'Item Number: 12345'

     >>> fstr("Item Number: {0:i}").format(pos_int)
     'Item Number: 12345'

     >>> fstr("Item Number: {0:x}").format(pos_int)
     'Item Number: 0x3039'

     >>> fstr("Item Number: {0:X}").format(pos_int)
     'Item Number: 0X3039'

     >>> fstr("Item Number: {0:o}").format(pos_int)
     'Item Number: 030071'

     >>> fstr("Item Number: {0:>10}").format(pos_int)
     'Item Number:      12345'

     >>> fstr("Item Number: {0:<10}").format(pos_int)
     'Item Number: 12345     '

     >>> fstr("Item Number: {0:^10}").format(pos_int)
     'Item Number:   12345   '

     >>> fstr("Item Number: {0:i010%}").format(neg_int)
     'Item Number: -0001234500 %'

     >>> fstr("Item Number: {0:i()}").format(pos_int)
     'Item Number:  12345 '

     >>> fstr("Item Number: {0:i()}").format(neg_int)
     'Item Number: (12345)'


     FIXEDPOINT:

     >>> fstr("Item Number: {0}").format(pos_float)
     'Item Number: 123.45678'

     >>> fstr("Item Number: {0:f}").format(pos_float)
     'Item Number: 123.45678'

     >>> fstr("Item Number: {0:>12}").format(pos_float)
     'Item Number:    123.45678'

     >>> fstr("Item Number: {0:<12}").format(pos_float)
     'Item Number: 123.45678   '

     >>> fstr("Item Number: {0:^12}").format(pos_float)
     'Item Number:  123.45678  '

     >>> fstr("Item Number: {0:f07.2%}").format(neg_float)
     'Item Number: -0012345.68 %'

     >>> fstr("Item Number: {0:F.3}").format(neg_float)
     'Item Number: -123.457'

     >>> fstr("Item Number: {0:f.7}").format(neg_float)
     'Item Number: -123.4567800'

     >>> fstr("Item Number: {0:f(05.3)}").format(neg_float)
     'Item Number: (00123.457)'

     >>> fstr("Item Number: {0:f05.7}").format(neg_float)
     'Item Number: -00123.4567800'

     >>> fstr("Item Number: {0:f06.2}").format(neg_float)
     'Item Number: -000123.46'

     >>> import time
     >>> class GetTime(object):
     ...     def __init__(self, time=time.gmtime()):
     ...         self.time = time
     ...     def __format__(self, spec):
     ...         return fstr(time.strftime(spec, self.time))

     >>> start = GetTime(time.gmtime(1187154773.0085449))

     >>> fstr("Start: {0:%d/%m/%Y %H:%M:%S,<30}").format(start)
     'Start: 15/08/2007 05:12:53           '


-----------------------------------------------------------------
Examples from python3000 list:
     (With only a few changes where it makes sense or to make it work.)

     >>> floatvalue = ffloat(123.456)

     # Floating point number of natural width
     >>> fstr('{0:f}').format(floatvalue)
     '123.456'

     # Floating point number, with 10 digits before the decimal
     >>> fstr('{0:f10}').format(floatvalue)
     '       123.456'

     # Floating point number, in feild of width 10, right justified.
     >>> fstr('{0:f,>10}').format(floatvalue)
     '   123.456'

     # Floating point number, width at least 10 digits before
     # the decimal, leading zeros
     >>> fstr('{0:f010}').format(floatvalue)
     '0000000123.456'

     # Floating point number with two decimal digits
     >>> fstr('{0:f.2}').format(floatvalue)
     '123.46'

     # Minimum width 8, type defaults to natural type
     >>> fstr('{0:8}').format(floatvalue)
     '123.456 '

     # Integer number, 5 digits, sign always shown
     >>> fstr('{0:d+5}').format(floatvalue)
     '+  123'

     # repr() format
     >>> fstr('{0:r}').format(floatvalue)
     "'123.456'"

     # Field width 10, repr() format
     >>> fstr('{0:r10}').format(floatvalue)
     "'123.456' "

     # String right-aligned within field of minimum width
     >>> fstr('{0:s10}').format(floatvalue)
     '123.456   '

     # String right-aligned within field of minimum width
     >>> fstr('{0:s+10,10}').format(floatvalue)
     '123.456   '

     # String left-aligned in 10 char (min) field.
     >>> fstr('{0:s<10}').format(floatvalue)
     '123.456   '

     # Integer centered in 15 character field
     >>> fstr('{0:d,^15}').format(floatvalue)
     '      123      '

     # Right align and pad with '.' chars
     >>> fstr('{0:>15/.}').format(floatvalue)
     '........123.456'

     # Floating point,  always show sign,
     # leading zeros, 10 digits before decimal, 5 decimal places.
     >>> fstr('{0:f010.5}').format(floatvalue)
     '0000000123.45600'






More information about the Python-3000 mailing list