Skip to content

Commit a725959

Browse files
committed
SF bug 433228: repr(list) woes when len(list) big.
Gave Python linear-time repr() implementations for dicts, lists, strings. This means, e.g., that repr(range(50000)) is no longer 50x slower than pprint.pprint() in 2.2 <wink>. I don't consider this a bugfix candidate, as it's a performance boost. Added _PyString_Join() to the internal string API. If we want that in the public API, fine, but then it requires runtime error checks instead of asserts.
1 parent 239508c commit a725959

5 files changed

Lines changed: 193 additions & 56 deletions

File tree

Include/stringobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ extern DL_IMPORT(void) _Py_ReleaseInternedStrings(void);
7777
#define PyString_AS_STRING(op) (((PyStringObject *)(op))->ob_sval)
7878
#define PyString_GET_SIZE(op) (((PyStringObject *)(op))->ob_size)
7979

80+
/* _PyString_Join(sep, x) is like sep.join(x). sep must be PyStringObject*,
81+
x must be an iterable object. */
82+
extern DL_IMPORT(PyObject *) _PyString_Join(PyObject *sep, PyObject *x);
83+
8084
/* --- Generic Codecs ----------------------------------------------------- */
8185

8286
/* Create an object by decoding the encoded string s of the

Objects/dictobject.c

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -809,42 +809,80 @@ dict_print(register dictobject *mp, register FILE *fp, register int flags)
809809
static PyObject *
810810
dict_repr(dictobject *mp)
811811
{
812-
auto PyObject *v;
813-
PyObject *sepa, *colon;
814-
register int i;
815-
register int any;
812+
int i, pos;
813+
PyObject *s, *temp, *colon = NULL;
814+
PyObject *pieces = NULL, *result = NULL;
815+
PyObject *key, *value;
816816

817-
i = Py_ReprEnter((PyObject*)mp);
817+
i = Py_ReprEnter((PyObject *)mp);
818818
if (i != 0) {
819-
if (i > 0)
820-
return PyString_FromString("{...}");
821-
return NULL;
819+
return i > 0 ? PyString_FromString("{...}") : NULL;
822820
}
823821

824-
v = PyString_FromString("{");
825-
sepa = PyString_FromString(", ");
826-
colon = PyString_FromString(": ");
827-
any = 0;
828-
for (i = 0; i <= mp->ma_mask && v; i++) {
829-
dictentry *ep = mp->ma_table + i;
830-
PyObject *pvalue = ep->me_value;
831-
if (pvalue != NULL) {
832-
/* Prevent PyObject_Repr from deleting value during
833-
key format */
834-
Py_INCREF(pvalue);
835-
if (any++)
836-
PyString_Concat(&v, sepa);
837-
PyString_ConcatAndDel(&v, PyObject_Repr(ep->me_key));
838-
PyString_Concat(&v, colon);
839-
PyString_ConcatAndDel(&v, PyObject_Repr(pvalue));
840-
Py_DECREF(pvalue);
841-
}
822+
if (mp->ma_used == 0) {
823+
result = PyString_FromString("{}");
824+
goto Done;
842825
}
843-
PyString_ConcatAndDel(&v, PyString_FromString("}"));
844-
Py_ReprLeave((PyObject*)mp);
845-
Py_XDECREF(sepa);
826+
827+
pieces = PyList_New(0);
828+
if (pieces == NULL)
829+
goto Done;
830+
831+
colon = PyString_FromString(": ");
832+
if (colon == NULL)
833+
goto Done;
834+
835+
/* Do repr() on each key+value pair, and insert ": " between them.
836+
Note that repr may mutate the dict. */
837+
pos = 0;
838+
while (PyDict_Next((PyObject *)mp, &pos, &key, &value)) {
839+
int status;
840+
/* Prevent repr from deleting value during key format. */
841+
Py_INCREF(value);
842+
s = PyObject_Repr(key);
843+
PyString_Concat(&s, colon);
844+
PyString_ConcatAndDel(&s, PyObject_Repr(value));
845+
Py_DECREF(value);
846+
if (s == NULL)
847+
goto Done;
848+
status = PyList_Append(pieces, s);
849+
Py_DECREF(s); /* append created a new ref */
850+
if (status < 0)
851+
goto Done;
852+
}
853+
854+
/* Add "{}" decorations to the first and last items. */
855+
assert(PyList_GET_SIZE(pieces) > 0);
856+
s = PyString_FromString("{");
857+
if (s == NULL)
858+
goto Done;
859+
temp = PyList_GET_ITEM(pieces, 0);
860+
PyString_ConcatAndDel(&s, temp);
861+
PyList_SET_ITEM(pieces, 0, s);
862+
if (s == NULL)
863+
goto Done;
864+
865+
s = PyString_FromString("}");
866+
if (s == NULL)
867+
goto Done;
868+
temp = PyList_GET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1);
869+
PyString_ConcatAndDel(&temp, s);
870+
PyList_SET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1, temp);
871+
if (temp == NULL)
872+
goto Done;
873+
874+
/* Paste them all together with ", " between. */
875+
s = PyString_FromString(", ");
876+
if (s == NULL)
877+
goto Done;
878+
result = _PyString_Join(s, pieces);
879+
Py_DECREF(s);
880+
881+
Done:
882+
Py_XDECREF(pieces);
846883
Py_XDECREF(colon);
847-
return v;
884+
Py_ReprLeave((PyObject *)mp);
885+
return result;
848886
}
849887

850888
static int

Objects/listobject.c

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -246,26 +246,68 @@ list_print(PyListObject *op, FILE *fp, int flags)
246246
static PyObject *
247247
list_repr(PyListObject *v)
248248
{
249-
PyObject *s, *comma;
250249
int i;
250+
PyObject *s, *temp;
251+
PyObject *pieces = NULL, *result = NULL;
251252

252253
i = Py_ReprEnter((PyObject*)v);
253254
if (i != 0) {
254-
if (i > 0)
255-
return PyString_FromString("[...]");
256-
return NULL;
255+
return i > 0 ? PyString_FromString("[...]") : NULL;
257256
}
258-
s = PyString_FromString("[");
259-
comma = PyString_FromString(", ");
260-
for (i = 0; i < v->ob_size && s != NULL; i++) {
261-
if (i > 0)
262-
PyString_Concat(&s, comma);
263-
PyString_ConcatAndDel(&s, PyObject_Repr(v->ob_item[i]));
257+
258+
if (v->ob_size == 0) {
259+
result = PyString_FromString("[]");
260+
goto Done;
264261
}
265-
Py_XDECREF(comma);
266-
PyString_ConcatAndDel(&s, PyString_FromString("]"));
262+
263+
pieces = PyList_New(0);
264+
if (pieces == NULL)
265+
goto Done;
266+
267+
/* Do repr() on each element. Note that this may mutate the list,
268+
so must refetch the list size on each iteration. */
269+
for (i = 0; i < v->ob_size; ++i) {
270+
int status;
271+
s = PyObject_Repr(v->ob_item[i]);
272+
if (s == NULL)
273+
goto Done;
274+
status = PyList_Append(pieces, s);
275+
Py_DECREF(s); /* append created a new ref */
276+
if (status < 0)
277+
goto Done;
278+
}
279+
280+
/* Add "[]" decorations to the first and last items. */
281+
assert(PyList_GET_SIZE(pieces) > 0);
282+
s = PyString_FromString("[");
283+
if (s == NULL)
284+
goto Done;
285+
temp = PyList_GET_ITEM(pieces, 0);
286+
PyString_ConcatAndDel(&s, temp);
287+
PyList_SET_ITEM(pieces, 0, s);
288+
if (s == NULL)
289+
goto Done;
290+
291+
s = PyString_FromString("]");
292+
if (s == NULL)
293+
goto Done;
294+
temp = PyList_GET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1);
295+
PyString_ConcatAndDel(&temp, s);
296+
PyList_SET_ITEM(pieces, PyList_GET_SIZE(pieces) - 1, temp);
297+
if (temp == NULL)
298+
goto Done;
299+
300+
/* Paste them all together with ", " between. */
301+
s = PyString_FromString(", ");
302+
if (s == NULL)
303+
goto Done;
304+
result = _PyString_Join(s, pieces);
305+
Py_DECREF(s);
306+
307+
Done:
308+
Py_XDECREF(pieces);
267309
Py_ReprLeave((PyObject *)v);
268-
return s;
310+
return result;
269311
}
270312

271313
static int

Objects/stringobject.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,23 @@ string_join(PyStringObject *self, PyObject *args)
10311031
return res;
10321032
}
10331033

1034+
PyObject *_PyString_Join(PyObject *sep, PyObject *x)
1035+
{
1036+
PyObject* args;
1037+
PyObject* result = NULL;
1038+
1039+
assert(sep != NULL && PyString_Check(sep));
1040+
assert(x != NULL);
1041+
args = PyTuple_New(1);
1042+
if (args != NULL) {
1043+
Py_INCREF(x);
1044+
PyTuple_SET_ITEM(args, 0, x);
1045+
result = string_join((PyStringObject *)sep, args);
1046+
Py_DECREF(args);
1047+
}
1048+
return result;
1049+
}
1050+
10341051
static long
10351052
string_find_internal(PyStringObject *self, PyObject *args, int dir)
10361053
{

Objects/tupleobject.c

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -184,20 +184,56 @@ tupleprint(PyTupleObject *op, FILE *fp, int flags)
184184
static PyObject *
185185
tuplerepr(PyTupleObject *v)
186186
{
187-
PyObject *s, *comma;
188-
int i;
187+
int i, n;
188+
PyObject *s, *temp;
189+
PyObject *pieces, *result = NULL;
190+
191+
n = v->ob_size;
192+
if (n == 0)
193+
return PyString_FromString("()");
194+
195+
pieces = PyTuple_New(n);
196+
if (pieces == NULL)
197+
return NULL;
198+
199+
/* Do repr() on each element. */
200+
for (i = 0; i < n; ++i) {
201+
s = PyObject_Repr(v->ob_item[i]);
202+
if (s == NULL)
203+
goto Done;
204+
PyTuple_SET_ITEM(pieces, i, s);
205+
}
206+
207+
/* Add "()" decorations to the first and last items. */
208+
assert(n > 0);
189209
s = PyString_FromString("(");
190-
comma = PyString_FromString(", ");
191-
for (i = 0; i < v->ob_size && s != NULL; i++) {
192-
if (i > 0)
193-
PyString_Concat(&s, comma);
194-
PyString_ConcatAndDel(&s, PyObject_Repr(v->ob_item[i]));
195-
}
196-
Py_DECREF(comma);
197-
if (v->ob_size == 1)
198-
PyString_ConcatAndDel(&s, PyString_FromString(","));
199-
PyString_ConcatAndDel(&s, PyString_FromString(")"));
200-
return s;
210+
if (s == NULL)
211+
goto Done;
212+
temp = PyTuple_GET_ITEM(pieces, 0);
213+
PyString_ConcatAndDel(&s, temp);
214+
PyTuple_SET_ITEM(pieces, 0, s);
215+
if (s == NULL)
216+
goto Done;
217+
218+
s = PyString_FromString(n == 1 ? ",)" : ")");
219+
if (s == NULL)
220+
goto Done;
221+
temp = PyTuple_GET_ITEM(pieces, n-1);
222+
PyString_ConcatAndDel(&temp, s);
223+
PyTuple_SET_ITEM(pieces, n-1, temp);
224+
if (temp == NULL)
225+
goto Done;
226+
227+
/* Paste them all together with ", " between. */
228+
s = PyString_FromString(", ");
229+
if (s == NULL)
230+
goto Done;
231+
result = _PyString_Join(s, pieces);
232+
Py_DECREF(s);
233+
234+
Done:
235+
Py_DECREF(pieces);
236+
return result;
201237
}
202238

203239
static long

0 commit comments

Comments
 (0)