To further clarify. The underlying problem here, I believe, is a fault in the JSON specification, which does not allow for the valid Javascript values of NaN, Infinity, and -Infinity. The Python team made a design decision to allow for those values since they are, after all, valid Javascript. There is probably a good reason that these values were left out of the JSON spec, but I do not know what it is, and in my ignorance I am calling it a fault.
Now when I say that the Python choice is not the design decision I would have made, I mean that my inclination would be to make the default calls to functions in the json module to produce only valid JSON (despite my belief that those values should have been included in the JSON spec). I think it's great that there is an option to produce this handy valid Javascript version -- I'm just not sure it should be the default.
However, the way it is happens to be simpler for most use cases. In particular, if you are producing JSON with Python's json module and then consuming it directly in a Javascript environment, all is likely to go well. Problems start happening, rather, when you start consuming that JSON with a library that expects valid JSON -- which is more strict that Javascript. In my case, this was happening with Java.
Thanks for the comment. I was inclined to think that this was a design decision and not a bug, so I did a bit more digging on the issue. It turns out that this is documented pretty clearly in the JSON module. The solution is to pass allow_nan=False into the call to dump/dumps:
"If allow_nan is False (default: True), then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity)."
So, this is a feature, not a bug. I'm not sure that this is the design choice I would have made, but it is what it is. If you want a JSON-valid representation that allows for Nan, and the infinities, you will need to roll your own decoder to convert those values to strings, or however you want to represent those values.
The json module documentation is here: http://docs.python.org/2/library/json.html
You are right. I was forgetting that concatenation does not do any coercion, even for types that have obvious string representations.
I did not mean to imply that None is getting some special treatment here. The str call is the definition of using the %()s operator. One can also call repr with %()r. However, None seems particularly problematic in that, in most cases, it seems like you really don't want 'None' to sneak into your presentation code. Whereas, other types have available explicit replacement operators (%d, %f, etc.) which provide a certain kind of protection for those types, I think it is unfortunate that there is not an explicit replacement operator for the string type also.
There could just as well have been a design decision to call int() on subjects to the %d operator -- but it doesn't. So, there does seem to be some design inconsistency here worth being aware of.
Thanks @aloschilov. Your comment is the true pro tip here. Thank you for the correction!