diff -r dfcb64f51f7b Doc/library/functions.rst --- a/Doc/library/functions.rst Wed Jun 05 12:20:24 2013 +0200 +++ b/Doc/library/functions.rst Wed Jun 05 19:54:49 2013 -0400 @@ -753,7 +753,7 @@ already arranged into argument tuples, see :func:`itertools.starmap`\. -.. function:: max(iterable, *[, key]) +.. function:: max(iterable, *[, default, key]) max(arg1, arg2, *args[, key]) Return the largest item in an iterable or the largest of two or more @@ -764,8 +764,12 @@ in the iterable is returned. If two or more positional arguments are provided, the largest of the positional arguments is returned. - The optional keyword-only *key* argument specifies a one-argument ordering - function like that used for :meth:`list.sort`. + There are two optional keyword-only arguments. The *key* argument specifies + a one-argument ordering function like that used for :meth:`list.sort`. The + *default* argument specifies an object to return if the provided iterable is + empty. If the iterable is empty and *default* is unprovided, a + :exc:`ValueError` is raised. The *key* function will not be called on the + *default*, it is simply returned as-is. If multiple items are maximal, the function returns the first one encountered. This is consistent with other sort-stability preserving tools @@ -781,7 +785,7 @@ :ref:`typememoryview` for more information. -.. function:: min(iterable, *[, key]) +.. function:: min(iterable, *[, default, key]) min(arg1, arg2, *args[, key]) Return the smallest item in an iterable or the smallest of two or more @@ -792,8 +796,12 @@ in the iterable is returned. If two or more positional arguments are provided, the smallest of the positional arguments is returned. - The optional keyword-only *key* argument specifies a one-argument ordering - function like that used for :meth:`list.sort`. + There are two optional keyword-only arguments. The *key* argument specifies + a one-argument ordering function like that used for :meth:`list.sort`. The + *default* argument specifies an object to return if the provided iterable is + empty. If the iterable is empty and *default* is unprovided, a + :exc:`ValueError` is raised. The *key* function will not be called on the + *default*, it is simply returned as-is. If multiple items are minimal, the function returns the first one encountered. This is consistent with other sort-stability preserving tools diff -r dfcb64f51f7b Lib/test/test_builtin.py --- a/Lib/test/test_builtin.py Wed Jun 05 12:20:24 2013 +0200 +++ b/Lib/test/test_builtin.py Wed Jun 05 19:54:49 2013 -0400 @@ -844,8 +844,19 @@ self.assertEqual(max(1, 2.0, 3), 3) self.assertEqual(max(1.0, 2, 3), 3) + self.assertRaises(TypeError, min) + self.assertRaises(TypeError, min, 42) + self.assertRaises(ValueError, min, ()) + class BadSeq: + def __getitem__(self, index): + raise ValueError + self.assertRaises(ValueError, min, BadSeq()) + for stmt in ( "max(key=int)", # no args + "max(default=None)", + "max(1, 2, default=None)", # require container for default + "max(default=None, key=int)", "max(1, key=int)", # single arg not iterable "max(1, 2, keystone=int)", # wrong keyword "max(1, 2, key=int, abc=int)", # two many keywords @@ -862,6 +873,13 @@ self.assertEqual(max((1,2), key=neg), 1) # two elem iterable self.assertEqual(max(1, 2, key=neg), 1) # two elems + self.assertEqual(max((), default=None), None) # zero elem iterable + self.assertEqual(max((1,), default=None), 1) # one elem iterable + self.assertEqual(max((1,2), default=None), 2) # two elem iterable + + self.assertEqual(max((), default=1, key=neg), 1) + self.assertEqual(max((1, 2), default=1, key=neg), 1) + data = [random.randrange(200) for i in range(100)] keys = dict((elem, random.randrange(50)) for elem in data) f = keys.__getitem__ @@ -888,6 +906,9 @@ for stmt in ( "min(key=int)", # no args + "min(default=None)", + "min(1, 2, default=None)", # require container for default + "min(default=None, key=int)", "min(1, key=int)", # single arg not iterable "min(1, 2, keystone=int)", # wrong keyword "min(1, 2, key=int, abc=int)", # two many keywords @@ -904,6 +925,13 @@ self.assertEqual(min((1,2), key=neg), 2) # two elem iterable self.assertEqual(min(1, 2, key=neg), 2) # two elems + self.assertEqual(min((), default=None), None) # zero elem iterable + self.assertEqual(min((1,), default=None), 1) # one elem iterable + self.assertEqual(min((1,2), default=None), 1) # two elem iterable + + self.assertEqual(min((), default=1, key=neg), 1) + self.assertEqual(min((1, 2), default=1, key=neg), 2) + data = [random.randrange(200) for i in range(100)] keys = dict((elem, random.randrange(50)) for elem in data) f = keys.__getitem__ diff -r dfcb64f51f7b Python/bltinmodule.c --- a/Python/bltinmodule.c Wed Jun 05 12:20:24 2013 +0200 +++ b/Python/bltinmodule.c Wed Jun 05 19:54:49 2013 -0400 @@ -1329,26 +1329,35 @@ min_max(PyObject *args, PyObject *kwds, int op) { PyObject *v, *it, *item, *val, *maxitem, *maxval, *keyfunc=NULL; + PyObject *emptytuple, *defaultval = NULL; + static char *kwlist[] = {"key", "default", NULL}; const char *name = op == Py_LT ? "min" : "max"; + int positional = PyTuple_Size(args) > 1; + int ret; - if (PyTuple_Size(args) > 1) + if (positional) v = args; else if (!PyArg_UnpackTuple(args, (char *)name, 1, 1, &v)) return NULL; - if (kwds != NULL && PyDict_Check(kwds) && PyDict_Size(kwds)) { - keyfunc = PyDict_GetItemString(kwds, "key"); - if (PyDict_Size(kwds)!=1 || keyfunc == NULL) { - PyErr_Format(PyExc_TypeError, - "%s() got an unexpected keyword argument", name); - return NULL; - } - Py_INCREF(keyfunc); + emptytuple = PyTuple_New(0); + if (emptytuple == NULL) + return NULL; + ret = PyArg_ParseTupleAndKeywords(emptytuple, kwds, "|$OO", kwlist, + &keyfunc, &defaultval); + Py_DECREF(emptytuple); + if (!ret) + return NULL; + + if (positional && defaultval != NULL) { + PyErr_Format(PyExc_TypeError, + "Cannot specify a default for %s() with multiple " + "positional arguments", name); + return NULL; } it = PyObject_GetIter(v); if (it == NULL) { - Py_XDECREF(keyfunc); return NULL; } @@ -1392,14 +1401,18 @@ if (PyErr_Occurred()) goto Fail_it; if (maxval == NULL) { - PyErr_Format(PyExc_ValueError, - "%s() arg is an empty sequence", name); assert(maxitem == NULL); + if (defaultval != NULL) { + Py_INCREF(defaultval); + maxitem = defaultval; + } else { + PyErr_Format(PyExc_ValueError, + "%s() arg is an empty sequence", name); + } } else Py_DECREF(maxval); Py_DECREF(it); - Py_XDECREF(keyfunc); return maxitem; Fail_it_item_and_val: @@ -1410,7 +1423,6 @@ Py_XDECREF(maxval); Py_XDECREF(maxitem); Py_DECREF(it); - Py_XDECREF(keyfunc); return NULL; }