From 9cdf70399f9d61434741f1efb3eff01bfa26f656 Mon Sep 17 00:00:00 2001 From: Raymond Hettinger Date: Fri, 29 Dec 2006 18:49:13 +0000 Subject: [PATCH] For sets with cyclical reprs, emit '...' instead of recursing. --- Lib/test/test_set.py | 26 ++++++++++++++++++++++++++ Misc/NEWS | 3 +++ Objects/setobject.c | 28 ++++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py index 422cc41c256..dedd1fb5e59 100644 --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -21,6 +21,11 @@ class BadCmp: def __cmp__(self, other): raise RuntimeError +class ReprWrapper: + 'Used to test self-referential repr() calls' + def __repr__(self): + return repr(self.value) + class TestJointOps(unittest.TestCase): # Tests common to both set and frozenset @@ -244,6 +249,27 @@ class TestJointOps(unittest.TestCase): self.assertRaises(RuntimeError, s.discard, BadCmp()) self.assertRaises(RuntimeError, s.remove, BadCmp()) + def test_cyclical_repr(self): + w = ReprWrapper() + s = self.thetype([w]) + w.value = s + name = repr(s).partition('(')[0] # strip class name from repr string + self.assertEqual(repr(s), '%s([%s(...)])' % (name, name)) + + def test_cyclical_print(self): + w = ReprWrapper() + s = self.thetype([w]) + w.value = s + try: + fo = open(test_support.TESTFN, "wb") + print >> fo, s, + fo.close() + fo = open(test_support.TESTFN, "rb") + self.assertEqual(fo.read(), repr(s)) + finally: + fo.close() + os.remove(test_support.TESTFN) + class TestSet(TestJointOps): thetype = set diff --git a/Misc/NEWS b/Misc/NEWS index 9acc77194b8..7ab30ed2685 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,6 +18,9 @@ Core and builtins custom ``__eq__()`` method to confuse set internals when class instances were used as a set's elements and the ``__eq__()`` method mutated the set. +- The repr for self-referential sets and fronzensets now shows "..." instead + of falling into infinite recursion. + - Eliminated unnecessary repeated calls to hash() by set.intersection() and set.symmetric_difference_update(). diff --git a/Objects/setobject.c b/Objects/setobject.c index d33fff93fe2..507a07bdcc7 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -572,34 +572,54 @@ set_tp_print(PySetObject *so, FILE *fp, int flags) Py_ssize_t pos=0; char *emit = ""; /* No separator emitted on first pass */ char *separator = ", "; + int status = Py_ReprEnter((PyObject*)so); + + if (status != 0) { + if (status < 0) + return status; + fprintf(fp, "%s(...)", so->ob_type->tp_name); + return 0; + } fprintf(fp, "%s([", so->ob_type->tp_name); while (set_next(so, &pos, &entry)) { fputs(emit, fp); emit = separator; - if (PyObject_Print(entry->key, fp, 0) != 0) + if (PyObject_Print(entry->key, fp, 0) != 0) { + Py_ReprLeave((PyObject*)so); return -1; + } } fputs("])", fp); + Py_ReprLeave((PyObject*)so); return 0; } static PyObject * set_repr(PySetObject *so) { - PyObject *keys, *result, *listrepr; + PyObject *keys, *result=NULL, *listrepr; + int status = Py_ReprEnter((PyObject*)so); + + if (status != 0) { + if (status < 0) + return NULL; + return PyString_FromFormat("%s(...)", so->ob_type->tp_name); + } keys = PySequence_List((PyObject *)so); if (keys == NULL) - return NULL; + goto done; listrepr = PyObject_Repr(keys); Py_DECREF(keys); if (listrepr == NULL) - return NULL; + goto done; result = PyString_FromFormat("%s(%s)", so->ob_type->tp_name, PyString_AS_STRING(listrepr)); Py_DECREF(listrepr); +done: + Py_ReprLeave((PyObject*)so); return result; }