

PyFormat – Practical examples of old and new style string formatting - hgezim
http://pyformat.info

======
sago
A good reference.

My take home is that every single version that is possible to do in old style
formatting is longer in new style formatting. Even more so if the old-style
formatting were written without the extraneous 1-ples.

    
    
        '%4d' % 42
    

vs

    
    
        '{:4d}'.format(42)
    

It isn't surprising that so much code is still being written in old-style. The
advantages of the new style, like padding with arbitrary characters; value
reuse (particularly for I18n, though that tends to use a different mechanism
for production systems that need it); and nested display; are really quite
minor.

This feeds into the other python issue on HN today - why does py3 have so
little uptake? Because it is a solution to a problem few people have.

~~~
mixmastamyk
Yes, very nice, helpful site.

.format() is mostly too verbose for my tastes. Even Guido himself put it into
the "meh" category in a talk. To be clear, I do use it when I need quick
lightweight templating, say building a very complicated string.

But for most simple output printf style is shorter, simpler and I know it by
heart. There was a guy at work who insisted on using .format() for everything
that soured me on it, sometimes even simple string concatenation:

    
    
        print 'result: {long_var_name}'.format({long_var_name=long_var_name})
    

Sometimes lines reached 200 chars. If lines were broken down, they might
extend to 4 lines for each debug statement, adding tons of clutter. Said it
was more readable because you could ignore the end. :/

As sago said, printf is more concise in the majority of use cases. Not to
mention the logging niceties:

    
    
        log.info('result: %s %s', one, two)  # shorter and note that fmt is deferred!
    

A _big_ one in my eyes. Too bad logging doesn't support {} internally.

I've also wondered if .format could be improved if the % operator could be
used with it, given appropriate precedence rules. Or perhaps use another
char... dunno & or /?

    
    
        print 'results: {} {}' %  results
        print 'results: {} {}' %% results
        print 'results: {} {}' /  results
    

Pycon, are you listening? ;)

~~~
dalke
This is what your co-worker does now:

    
    
      'result: {long_var_name}'.format(long_var_name=long_var_name)
    

A similar option is possible with '%'-style formatting:

    
    
      'result: %(long_var_name)s'%{"long_var_name":long_var_name}
        -or-
      'result: %(long_var_name)s'%dict(long_var_name=long_var_name)
    

The first of these alternatives is indeed shorter, so long as there's only a
single parameter. If there are multiple parameters then the .format() solution
will be shorter.

My experience with '%(name)s' % dict formatting is that I would often forget
the terminal 's', and write it as '%(name)'. For template-like strings, where
I want named substitutions, I am very much in favor of {}-style formatting.

So if your co-worker insists on named substitutions, then s.format() is less
verbose than s % dict.

Regarding logging, there's a section of how to use format templates in the
logging system, at [https://docs.python.org/3/howto/logging-
cookbook.html#format...](https://docs.python.org/3/howto/logging-
cookbook.html#formatting-styles) . In addition to using a LogRecord factory,
you could reduce the overhead in old-style logging to only an extra
constructor calls:

    
    
      log.info(M('result: {} {}', one, two))
    

where M.__str__ forwards to str.format.

Personally, I would like a way to pre-compile the format string:

    
    
      fmt = format_compile("result: {} {} {}")
      for result in results:
        print(fmt(*result))
    

because currently (in Python 3.3) "%d %d %d" is about 40% faster than "{} {}
{}".

EDIT: Never mind: It can be faster to interpolate the string each time than to
call a constructor:

    
    
      % python3.3 -mtimeit 's="%s %s %s"' 's % (1, 3, 9)'
      1000000 loops, best of 3: 0.36 usec per loop
      % python3.3 -mtimeit 'class Spam:pass' 'Spam()'
      10000 loops, best of 3: 23.6 usec per loop
    

I did not expect a factor of 65!

~~~
zwegner
Your timeit calls in the edit need the -s parameter before the first
statements, so they aren't executed on each loop iteration. The overhead is in
creating the class, not the instances.

    
    
      $ python3 -mtimeit -s 's="%s %s %s"' 's % (1, 3, 9)'
      1000000 loops, best of 3: 0.343 usec per loop
      $ python3 -mtimeit -s 'class Spam:pass' 'Spam()'
      10000000 loops, best of 3: 0.106 usec per loop

~~~
dalke
Thanks! That's a big forehead smack on my side.

The numbers are more in line with what I expected. Meaning it _is_ possible to
defer some of the time cost by using a single constructor rather than
evaluating the string.

------
fragmede
They missed my favorite trick! locals() will give you a dict of, well, local
variables, which generally coincides with what you want, so:

    
    
        a = 4
        'A is: {a}'.format(**locals())
    

works as expected.

~~~
quarktasche
Might not work in some special cases though. Try:

    
    
      def foo():
          a = 4
          def bar():
              print("a = {a}".format(**locals()))
          bar()
            
      foo()
    

which will raise a KeyError, while it will work fine if you add a

    
    
      print(a)
    

to the end of bar().

~~~
gcr
In this case, 'a' isn't a local variable binding though.

~~~
pyre
Right, but it's accessible in scope though. It's just a "gotcha" for that
method that might be easy to overlook if you aren't paying attention (or if
someone with less experience stumbles onto this 'trick' in production code).

------
kstrauser
My last remaining complaint with new formatting is with Pylint and logging
messages. The old-style way is to write:

    
    
        logging.debug('some value: %d', 123)
    

which avoids the cost of string interpolation if that log message wasn't going
to be emitted, say because the log level is higher than DEBUG. If you instead
write:

    
    
        logging.debug('some value: {}'.format(123))
    

then Pylint will complain that I "Use % formatting in logging functions but
pass the % parameters as arguments (logging-format-interpolation)".

Yes, I can disable that particular warning, either by maintain a pylintrc file
or adding annotations to the source code. Yes, this is a dumb complaint. But
yes, it still bugs me.

------
andy_ppp
I really don't mind these apparent (small) improvements to Python.

However, the fact that Python 3 just went off and did it's own things rather
than the usual cycle of:

    
    
        1) improve a language feature
        2) deprecate the old way of doing it
        3) give people time to update code (usually a couple of point releases)
        4) remove features that have been replaced
    

Python 3 should not have dropped lots of little changes with no backwards
compatibility. They should still make 2.8 and 2.9 that are releases that
remove features and add new ones until most python code works in Python 3.

~~~
toyg
Somebody should write a HN plugin that does something like this:

    
    
        if is_about_python3(post):
            insert_token_random_complaint_from_2008()
    

(edit: snake-case)

~~~
andy_ppp
Can _someone_ write a filter for snarky unconstructive comments while they are
at it?

~~~
toyg
Just make sure not to filter tedious unconstructive 7-year-old lists of
grievances. We can't live without those.

~~~
andy_ppp
I really don't see what I said wasn't true, Debian still won't escape Python 2
for many years for example. Are you saying the move to Python 3 was smooth and
a resounding success or are you just pretending that it's done and no one is
still using Python 2.x?

Sorry my comment has annoyed you so much but you really haven't made
constructive points to disprove it.

------
infix
There should be two-- and preferably only two --obvious ways to do it.

\-- The Zen of Python

------
erichurkman
Python 2.7 also added a non-locale aware thousands separator that be be
combined with other options:

    
    
        >>> "{:>40,.2f}".format(sys.maxint)
        '            9,223,372,036,854,775,808.00'

------
jkbr

        Basic formatting
        Old: '%d %d' % (1, 2)
        New: '{} {}'.format(1, 2)
    

It should rather be `{:d} {:d}'.format(1, 2)` but even that isn't strictly
equivalent (try both styles with a float or a Decimal).

------
foxylad
I'm a c dinosaur, so I've always used old-style formatting because I know it
off by heart. Having said that, the alignment operator (<^>) reminds me of my
joy when learning python - power and simplicity!

------
auscompgeek
This fails to mention the str.format_map() shortcut method (new in Python
3.2). It's useful if you already have a dictionary of all your values!

------
lumpypua
Really cool and the site looks great too! Was a css framework used for the
styling or is it hand written? Can't tell from the minification.

------
civilian

        '%d %d' % (1, 2)
    

I would have done this as

    
    
        '%s %s' % (1, 2)
    

Because we're turning everything into a string at the end of the day anyway!

Yeah. Having finished the article, .format() just isn't really needed. If I'm
at the point where I'm doing templating with key:values, I'll be using jinja.

~~~
jkbr
%d converts the value to int first which might be useful:

    
    
        >>> '%d' % 3.14
        '3'
        >>> '%d' % 'foo'
        TypeError: %d format: a number is required, not str

------
mangeletti
I personally prefer the old style still, and I still don't see the value of
deprecating it in favor of the new style. I started Python in 2008 (p3k was
brand new), and I used new style formatting for about 2 years. After realizing
that I was the only one using new style I switched and now I cannot imagines
going back.

~~~
dorfsmay
I use the new style for three reasons:

• it is supposedly significantly faster

• new, means, eventually we'll have to use it, so might as well get used to it
now!

• it's a method on an object, which makes my brain happy. I never managed to
get my brain wrapped around the old style syntax, it's inconsistent with the
rest of the python syntax.

~~~
_ZeD_
_• it 's a method on an object, which makes my brain happy. I never managed to
get my brain wrapped around the old style syntax, it's inconsistent with the
rest of the python syntax._

uhh... so you'd prefer to do something like `1.add(2)` ???

~~~
jnpatel
While arithmetic syntactic sugar is useful since it's much clearer, I agree
that I'd often prefer a function call to some unintuitive symbol syntax.

Another example for me, in Python, is:

    
    
        print >> sys.stderr, 'foo' 
    

vs.

    
    
        print ('foo', file=sys.stderr)

~~~
dorfsmay
Ah yes, I've been doing

    
    
        from __future__ import print_function
    

pretty much since it has been available. flush=True, end='' etc.... much more
intuitive.

------
japaget
One caution that this document does not mention is that the new formatting is
available only in recent versions of Python. One of my projects runs on a
CentOS 5.x server, which has Python 2.4, so I had to convert all of the new
style formatting to old in order to get it to run there.

~~~
huxley
Wow, Python 2.4 was last updated on October 18, 2006.

Since Centos 5 still has about 2 years of life but fewer and fewer Python
packages support 2.4, it might be worth looking at Pyenv:

[https://github.com/yyuu/pyenv](https://github.com/yyuu/pyenv)

Good overview of what it lets you do:

[http://fgimian.github.io/blog/2014/04/20/better-python-
versi...](http://fgimian.github.io/blog/2014/04/20/better-python-version-and-
environment-management-with-pyenv/)

------
zenogais
Can't tell you how many times I've Googled this and had to dig through the
docs. Thank you!

------
jegutman
I use the format: foo = 'foo' bar = 'bar' print "%(foo)s%(bar)s" % locals()
which I guess is what this is similar too.

basically locals(), vars(), and globals() I find very useful for string
formatting.

------
gamesbrainiac
In Dash.app (on Mac), there's actually a nifty file that you can download that
shows you how all the different formatting options work.

Its good, but I think this is even better.

------
gcb0
Its very lame that the new format kept the bad design from C hacks.

Why not 'field: {:center :minsize=8 varname}'.format(varname=123) ?

~~~
roywiggins
Once you start formatting more than one variable, isn't your proposed syntax
going to be horribly long?

    
    
        '{:center :minsize=8 varname} {:center :minsize=8 varname2} {:center :minsize=8 varname2}'.format(varname=123, varname2=123,varname3=123)
    

instead of

    
    
        '{:^8} {:^8} {:^8}'.format(varname, varname2, varname3)
    

Concision is not always a good thing, but still...

~~~
gcb0
top one is still much more readable.

also, if you are going to repeat always the same, you can standardize it in a
string var and just use that instead of repeating.

and if you are NOT, then it beats '{:^8} {:*8} {:@8} {:☃8}' anytime, because
now you have to know and notice the change in those cryptic chars.

------
father_of_two
Hey, very nice. I knew about the "new" format() but didn't know it was so
powerful.

