[Python-Dev] summary of transitioning from % to {} formatting

Steven D'Aprano steve at pearwood.info
Sat Oct 3 20:01:56 CEST 2009

On Sun, 4 Oct 2009 01:41:36 am Steven Bethard wrote:
> I thought it might be useful for those who don't have time to read a
> million posts to have a summary of what's happened in the formatting
> discussion.
> The basic problem is that many APIs in the standard library and
> elsewhere support only %-formatting and not {}-formatting, e.g.
> logging.Formatter accepts::
>   logging.Formatter(fmt="%(asctime)s - %(name)s")
> but not::
>   logging.Formatter(fmt="{asctime} - {name}")

Why is this a problem? Is it because the APIs require extra 
functionality that only {} formatting can provide? Possibly, but I 
doubt it -- I expect that the reason is:

(1) Some people would like to deprecate % formatting, and they can't 
while the std lib uses % internally.

(2) Some APIs in the std lib are tightly coupled to their internal 
implementation, and so you can't change their implementation without 
changing the API as well.

Remove either of these issues, and the problem becomes a non-problem, 
and no action is needed. Personally, I'd like to see no further talk 
about deprecating % until at least Python 3.2, that is, the *earliest* 
we would need to solve this issue would be 3.3. As the Zen 
says, "Although never is often better than *right* now."

> There seems to be mostly agreement that these APIs should at least
> support both formatting styles, and a sizable group (including Guido)
> believe that %-formatting should eventually be phased out (e.g. by
> Python 4). 

-1 on that. Time will tell if I change my mind in a couple of years, but 
I suspect not -- for simple formatting, I far prefer %. Judging by the 
reaction on comp.lang.python when this has been discussed in the past, 
I think a large (or at least loud) proportion of Python programmers 
agree with me.

> There are a number of competing proposals on how to allow 
> such APIs to start accepting {}-format strings:
> * Add a parameter which declares the type of format string::
>     logging.Formatter(fmt="{asctime} - {name}", format=BRACES)
>   The API code would then switch between %-format and {}-format
>   based on the value of that parameter. If %-formatting is to be
>   deprecated, this could be done by first deprecating
>   format=PERCENTS and requiring format=BRACES, and then changing the
>   default to format=BRACES.


> * Create string subclasses which convert % use to .format calls::
>     __ = brace_fmt
>     logging.Formatter(fmt=__("{asctime} - {name}"))

There are a few problems with this approach:

- Nobody has yet demonstrated that this brace_fmt class is even 
possible. It should be easy to handle a restricted set of simple 
templates (e.g. of the form "%(name)s" only), but what of the full 
range of behaviour supported by % formatting?

- Even if it is doable, it is a wrapper class, which means the module 
will suffer a performance hit on every call. At this time, we have no 
idea what the magnitude of that hit will be, but .format() is already 
slower than % so it will likely be significant.

- It strikes me as hideously ugly. I for one would delay using it as 
long as possible, no matter how many DepreciationWarnings I got. I'd 
drag my feet and avoid changing and complain loudly and then become 
sullen and resentful when I couldn't avoid making the change. I'd much 
rather go straight from %-based templates to {} in a single step than 
have this Frankenstein monster intermediate.

> * Teach the API to accept callables as well as strings::
>     logging.Formatter(fmt="{asctime} - {name}".format)
>   The API code would just call the object with .format() style
>   arguments if a callable was given instead of a string.


> * Create translators between %-format and {}-format::
>     assert to_braces("%(asctime)s") == "{asctime}"
>     assert to_percents("{asctime}") == "%(asctime)s"

+1, assuming such translators are even possible. Being optimistic, such 
translators would have one additional benefit: they would enable 
modules to completely decouple the API they offer from their internal 
implementation, without paying a runtime cost on every call, just a 
single once-off translation at initialisation time.

In theory, this could mean that modules could, if they choose, continue 
to offer an API based on % long after str.__mod__ is removed from the 

Steven D'Aprano

More information about the Python-Dev mailing list