Formatting a number to scientific notation and saving it without surrounding quotes [duplicate]

I would like to represent floats in a JSON file in scientific notation using Python 3.6+. None of

import json

a = 0.001234567

print(json.dumps(a))

json.encoder.FLOAT_REPR = lambda x: "{:e}".format(x)
print(json.dumps(a))

json.encoder.c_make_encoder = None
json.encoder.FLOAT_REPR = lambda x: "{:e}".format(x)
print(json.dumps(a))

work: All three prints give

0.001234567

instead of the desired

1.234567e-03

(Note that the last version works at least in in Python 2.7.15rc1.)

The answer should work with lists of floats as well.

Any hints?


You have to add some special casing for dicts, lists, sets, etc., but by referencing abstract base classes from collections.abc, you avoid explicitly testing for specific types.

Note that the test for Sequence has to avoid matching on str types, since iterating over a str gives a bunch of 1-character strs, which are also iterable Sequences, and so on until you reach the recursion limit. I could not find an ABC that represents "a sequence container, but not a str".

(I also have to echo Alex Martelli's criticism from a related post, that having to do this much work just to format a particular type speaks to issues in the design of the classes in this module.)

import json
from collections.abc import Mapping, Sequence

a = 0.001234567

class ScientificNotationEncoder(json.JSONEncoder):
    def iterencode(self, o, _one_shot=False):
        if isinstance(o, float):
            return "{:e}".format(o)
        elif isinstance(o, Mapping):
            return "{{{}}}".format(', '.join('"{}" : {}'.format(str(ok), self.iterencode(ov))
                                             for ok, ov in o.items()))
        elif isinstance(o, Sequence) and not isinstance(o, str):
            return "[{}]".format(', '.join(map(self.iterencode, o)))
        return ', '.join(super().iterencode(o, _one_shot))

aout = json.dumps([a, a, "xyzzy", 42, {'z': a}, (a, a, a),],
                  cls=ScientificNotationEncoder)
print(aout)

# loading back in seems to still work okay!
print(json.loads(aout))

Prints:

[1.234567e-03, 1.234567e-03, "xyzzy", 42, {"z" : 1.234567e-03}, [1.234567e-03, 1.234567e-03, 1.234567e-03]]
[0.001234567, 0.001234567, 'xyzzy', 42, {'z': 0.001234567}, [0.001234567, 0.001234567, 0.001234567]]