From e0805cf10ea84b44a13ad5649267edba7cb83ee9 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 10 Apr 2016 14:41:19 +0300 Subject: [PATCH] Issue #26719: More efficient formatting of ints and floats in json. --- Lib/json/encoder.py | 20 ++++++++-------- Modules/_json.c | 56 ++++----------------------------------------- 2 files changed, 14 insertions(+), 62 deletions(-) diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py index 26e9eb2bc69..d596489f42d 100644 --- a/Lib/json/encoder.py +++ b/Lib/json/encoder.py @@ -32,7 +32,6 @@ for i in range(0x20): #ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) INFINITY = float('inf') -FLOAT_REPR = repr def py_encode_basestring(s): """Return a JSON representation of a Python string @@ -221,7 +220,7 @@ class JSONEncoder(object): _encoder = encode_basestring def floatstr(o, allow_nan=self.allow_nan, - _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY): + _repr=float.__repr__, _inf=INFINITY, _neginf=-INFINITY): # Check for specials. Note that this type of test is processor # and/or platform-specific, so do tests which don't depend on the # internals. @@ -268,6 +267,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, list=list, str=str, tuple=tuple, + _intstr=int.__str__, ): if _indent is not None and not isinstance(_indent, str): @@ -309,10 +309,10 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, # Subclasses of int/float may override __str__, but we still # want to encode them as integers/floats in JSON. One example # within the standard library is IntEnum. - yield buf + str(int(value)) + yield buf + _intstr(value) elif isinstance(value, float): # see comment above for int - yield buf + _floatstr(float(value)) + yield buf + _floatstr(value) else: yield buf if isinstance(value, (list, tuple)): @@ -359,7 +359,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, # also allow them. Many encoders seem to do something like this. elif isinstance(key, float): # see comment for int/float in _make_iterencode - key = _floatstr(float(key)) + key = _floatstr(key) elif key is True: key = 'true' elif key is False: @@ -368,7 +368,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, key = 'null' elif isinstance(key, int): # see comment for int/float in _make_iterencode - key = str(int(key)) + key = _intstr(key) elif _skipkeys: continue else: @@ -389,10 +389,10 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, yield 'false' elif isinstance(value, int): # see comment for int/float in _make_iterencode - yield str(int(value)) + yield _intstr(value) elif isinstance(value, float): # see comment for int/float in _make_iterencode - yield _floatstr(float(value)) + yield _floatstr(value) else: if isinstance(value, (list, tuple)): chunks = _iterencode_list(value, _current_indent_level) @@ -419,10 +419,10 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, yield 'false' elif isinstance(o, int): # see comment for int/float in _make_iterencode - yield str(int(o)) + yield _intstr(o) elif isinstance(o, float): # see comment for int/float in _make_iterencode - yield _floatstr(float(o)) + yield _floatstr(o) elif isinstance(o, (list, tuple)): yield from _iterencode_list(o, _current_indent_level) elif isinstance(o, dict): diff --git a/Modules/_json.c b/Modules/_json.c index f63d758348d..f82af347cbb 100644 --- a/Modules/_json.c +++ b/Modules/_json.c @@ -116,8 +116,6 @@ raise_errmsg(char *msg, PyObject *s, Py_ssize_t end); static PyObject * encoder_encode_string(PyEncoderObject *s, PyObject *obj); static PyObject * -encoder_encode_long(PyEncoderObject* s UNUSED, PyObject *obj); -static PyObject * encoder_encode_float(PyEncoderObject *s, PyObject *obj); #define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"') @@ -1444,39 +1442,10 @@ _encoded_const(PyObject *obj) } } -static PyObject * -encoder_encode_long(PyEncoderObject* s UNUSED, PyObject *obj) -{ - /* Return the JSON representation of a PyLong and PyLong subclasses. - Calls int() on PyLong subclasses in case the str() was changed. - Added specifically to deal with IntEnum. See Issue18264. */ - PyObject *encoded, *longobj; - if (PyLong_CheckExact(obj)) { - encoded = PyObject_Str(obj); - } - else { - longobj = PyNumber_Long(obj); - if (longobj == NULL) { - PyErr_SetString( - PyExc_ValueError, - "Unable to coerce int subclass to int" - ); - return NULL; - } - encoded = PyObject_Str(longobj); - Py_DECREF(longobj); - } - return encoded; -} - - static PyObject * encoder_encode_float(PyEncoderObject *s, PyObject *obj) { - /* Return the JSON representation of a PyFloat. - Modified to call float() on float subclasses in case the subclass - changes the repr. See Issue18264. */ - PyObject *encoded, *floatobj; + /* Return the JSON representation of a PyFloat. */ double i = PyFloat_AS_DOUBLE(obj); if (!Py_IS_FINITE(i)) { if (!s->allow_nan) { @@ -1496,24 +1465,7 @@ encoder_encode_float(PyEncoderObject *s, PyObject *obj) return PyUnicode_FromString("NaN"); } } - /* coerce float subclasses to float (primarily for Enum) */ - if (PyFloat_CheckExact(obj)) { - /* Use a better float format here? */ - encoded = PyObject_Repr(obj); - } - else { - floatobj = PyNumber_Float(obj); - if (floatobj == NULL) { - PyErr_SetString( - PyExc_ValueError, - "Unable to coerce float subclass to float" - ); - return NULL; - } - encoded = PyObject_Repr(floatobj); - Py_DECREF(floatobj); - } - return encoded; + return PyFloat_Type.tp_repr(obj); } static PyObject * @@ -1557,7 +1509,7 @@ encoder_listencode_obj(PyEncoderObject *s, _PyAccu *acc, return _steal_accumulate(acc, encoded); } else if (PyLong_Check(obj)) { - PyObject *encoded = encoder_encode_long(s, obj); + PyObject *encoded = PyLong_Type.tp_str(obj); if (encoded == NULL) return -1; return _steal_accumulate(acc, encoded); @@ -1722,7 +1674,7 @@ encoder_listencode_dict(PyEncoderObject *s, _PyAccu *acc, goto bail; } else if (PyLong_Check(key)) { - kstr = encoder_encode_long(s, key); + kstr = PyLong_Type.tp_str(key); if (kstr == NULL) { goto bail; }