diff --git a/Lib/test/test_iter.py b/Lib/test/test_iter.py index 952ab663522..64fc01e19db 100644 --- a/Lib/test/test_iter.py +++ b/Lib/test/test_iter.py @@ -319,4 +319,39 @@ class TestCase(unittest.TestCase): self.assertEqual(filter(lambda x: not x, seq), [False]*25) self.assertEqual(filter(lambda x: not x, iter(seq)), [False]*25) + # Test max() and min()'s use of iterators. + def test_builtin_max_min(self): + self.assertEqual(max(SequenceClass(5)), 4) + self.assertEqual(min(SequenceClass(5)), 0) + self.assertEqual(max(8, -1), 8) + self.assertEqual(min(8, -1), -1) + + d = {"one": 1, "two": 2, "three": 3} + self.assertEqual(max(d), "two") + self.assertEqual(min(d), "one") + self.assertEqual(max(d.itervalues()), 3) + self.assertEqual(min(iter(d.itervalues())), 1) + + self.assertRaises(TypeError, list, list) + self.assertRaises(TypeError, list, 42) + + f = open(TESTFN, "w") + try: + f.write("medium line\n") + f.write("xtra large line\n") + f.write("itty-bitty line\n") + finally: + f.close() + f = open(TESTFN, "r") + try: + self.assertEqual(min(f), "itty-bitty line\n") + f.seek(0, 0) + self.assertEqual(max(f), "xtra large line\n") + finally: + f.close() + try: + unlink(TESTFN) + except OSError: + pass + run_unittest(TestCase) diff --git a/Misc/NEWS b/Misc/NEWS index bbd2ac3e014..9d84845744e 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -19,6 +19,8 @@ Core arguments: filter() list() + max() + min() What's New in Python 2.1 (final)? diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 1051374fcf2..9e8a2279c0d 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -1444,30 +1444,37 @@ static PyObject * min_max(PyObject *args, int op) { int i; - PyObject *v, *w, *x; - PySequenceMethods *sq; + PyObject *v, *w, *x, *it; if (PyTuple_Size(args) > 1) v = args; else if (!PyArg_ParseTuple(args, "O:min/max", &v)) return NULL; - sq = v->ob_type->tp_as_sequence; - if (sq == NULL || sq->sq_item == NULL) { - PyErr_SetString(PyExc_TypeError, - "min() or max() arg must be a sequence"); + + it = PyObject_GetIter(v); + if (it == NULL) return NULL; - } - w = NULL; + + w = NULL; /* the result */ for (i = 0; ; i++) { - x = (*sq->sq_item)(v, i); /* Implies INCREF */ + x = PyIter_Next(it); if (x == NULL) { - if (PyErr_ExceptionMatches(PyExc_IndexError)) { - PyErr_Clear(); - break; + /* We're out of here in any case, but if this is a + * StopIteration exception it's expected, but if + * any other kind of exception it's an error. + */ + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_StopIteration)) + PyErr_Clear(); + else { + Py_XDECREF(w); + Py_DECREF(it); + return NULL; + } } - Py_XDECREF(w); - return NULL; + break; } + if (w == NULL) w = x; else { @@ -1478,7 +1485,8 @@ min_max(PyObject *args, int op) } else if (cmp < 0) { Py_DECREF(x); - Py_XDECREF(w); + Py_DECREF(w); + Py_DECREF(it); return NULL; } else @@ -1488,6 +1496,7 @@ min_max(PyObject *args, int op) if (w == NULL) PyErr_SetString(PyExc_ValueError, "min() or max() arg is an empty sequence"); + Py_DECREF(it); return w; }