For sets with cyclical reprs, emit '...' instead of recursing.

This commit is contained in:
Raymond Hettinger 2006-12-29 18:49:13 +00:00
parent 04e820443b
commit 9cdf70399f
3 changed files with 53 additions and 4 deletions

View File

@ -21,6 +21,11 @@ class BadCmp:
def __cmp__(self, other): def __cmp__(self, other):
raise RuntimeError raise RuntimeError
class ReprWrapper:
'Used to test self-referential repr() calls'
def __repr__(self):
return repr(self.value)
class TestJointOps(unittest.TestCase): class TestJointOps(unittest.TestCase):
# Tests common to both set and frozenset # 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.discard, BadCmp())
self.assertRaises(RuntimeError, s.remove, 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): class TestSet(TestJointOps):
thetype = set thetype = set

View File

@ -18,6 +18,9 @@ Core and builtins
custom ``__eq__()`` method to confuse set internals when class instances custom ``__eq__()`` method to confuse set internals when class instances
were used as a set's elements and the ``__eq__()`` method mutated the set. 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 - Eliminated unnecessary repeated calls to hash() by set.intersection() and
set.symmetric_difference_update(). set.symmetric_difference_update().

View File

@ -572,34 +572,54 @@ set_tp_print(PySetObject *so, FILE *fp, int flags)
Py_ssize_t pos=0; Py_ssize_t pos=0;
char *emit = ""; /* No separator emitted on first pass */ char *emit = ""; /* No separator emitted on first pass */
char *separator = ", "; 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); fprintf(fp, "%s([", so->ob_type->tp_name);
while (set_next(so, &pos, &entry)) { while (set_next(so, &pos, &entry)) {
fputs(emit, fp); fputs(emit, fp);
emit = separator; emit = separator;
if (PyObject_Print(entry->key, fp, 0) != 0) if (PyObject_Print(entry->key, fp, 0) != 0) {
Py_ReprLeave((PyObject*)so);
return -1; return -1;
}
} }
fputs("])", fp); fputs("])", fp);
Py_ReprLeave((PyObject*)so);
return 0; return 0;
} }
static PyObject * static PyObject *
set_repr(PySetObject *so) 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); keys = PySequence_List((PyObject *)so);
if (keys == NULL) if (keys == NULL)
return NULL; goto done;
listrepr = PyObject_Repr(keys); listrepr = PyObject_Repr(keys);
Py_DECREF(keys); Py_DECREF(keys);
if (listrepr == NULL) if (listrepr == NULL)
return NULL; goto done;
result = PyString_FromFormat("%s(%s)", so->ob_type->tp_name, result = PyString_FromFormat("%s(%s)", so->ob_type->tp_name,
PyString_AS_STRING(listrepr)); PyString_AS_STRING(listrepr));
Py_DECREF(listrepr); Py_DECREF(listrepr);
done:
Py_ReprLeave((PyObject*)so);
return result; return result;
} }