Issue 18111: Add a default argument to min() and max()

This commit is contained in:
Raymond Hettinger 2013-06-24 22:43:02 -07:00
parent d7a034bd75
commit 4d6018fe45
4 changed files with 78 additions and 28 deletions

View File

@ -753,19 +753,22 @@ are always available. They are listed here in alphabetical order.
already arranged into argument tuples, see :func:`itertools.starmap`\. already arranged into argument tuples, see :func:`itertools.starmap`\.
.. function:: max(iterable, *[, key]) .. function:: max(iterable, *[, default, key])
max(arg1, arg2, *args[, key]) max(arg1, arg2, *args[, key])
Return the largest item in an iterable or the largest of two or more Return the largest item in an iterable or the largest of two or more
arguments. arguments.
If one positional argument is provided, *iterable* must be a non-empty If one positional argument is provided, it should be an :term:`iterable`.
iterable (such as a non-empty string, tuple or list). The largest item The largest item in the iterable is returned. If two or more positional
in the iterable is returned. If two or more positional arguments are arguments are provided, the smallest of the positional arguments is
provided, the largest of the positional arguments is returned. returned.
The optional keyword-only *key* argument specifies a one-argument ordering There are two optional keyword-only arguments. The *key* argument specifies
function like that used for :meth:`list.sort`. 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 not provided, a
:exc:`ValueError` is raised.
If multiple items are maximal, the function returns the first one If multiple items are maximal, the function returns the first one
encountered. This is consistent with other sort-stability preserving tools encountered. This is consistent with other sort-stability preserving tools
@ -781,19 +784,22 @@ are always available. They are listed here in alphabetical order.
:ref:`typememoryview` for more information. :ref:`typememoryview` for more information.
.. function:: min(iterable, *[, key]) .. function:: min(iterable, *[, default, key])
min(arg1, arg2, *args[, key]) min(arg1, arg2, *args[, key])
Return the smallest item in an iterable or the smallest of two or more Return the smallest item in an iterable or the smallest of two or more
arguments. arguments.
If one positional argument is provided, *iterable* must be a non-empty If one positional argument is provided, it should be an :term:`iterable`.
iterable (such as a non-empty string, tuple or list). The smallest item The smallest item in the iterable is returned. If two or more positional
in the iterable is returned. If two or more positional arguments are arguments are provided, the smallest of the positional arguments is
provided, the smallest of the positional arguments is returned. returned.
The optional keyword-only *key* argument specifies a one-argument ordering There are two optional keyword-only arguments. The *key* argument specifies
function like that used for :meth:`list.sort`. 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 not provided, a
:exc:`ValueError` is raised.
If multiple items are minimal, the function returns the first one If multiple items are minimal, the function returns the first one
encountered. This is consistent with other sort-stability preserving tools encountered. This is consistent with other sort-stability preserving tools

View File

@ -847,8 +847,19 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(max(1, 2.0, 3), 3) self.assertEqual(max(1, 2.0, 3), 3)
self.assertEqual(max(1.0, 2, 3), 3) self.assertEqual(max(1.0, 2, 3), 3)
self.assertRaises(TypeError, max)
self.assertRaises(TypeError, max, 42)
self.assertRaises(ValueError, max, ())
class BadSeq:
def __getitem__(self, index):
raise ValueError
self.assertRaises(ValueError, max, BadSeq())
for stmt in ( for stmt in (
"max(key=int)", # no args "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, key=int)", # single arg not iterable
"max(1, 2, keystone=int)", # wrong keyword "max(1, 2, keystone=int)", # wrong keyword
"max(1, 2, key=int, abc=int)", # two many keywords "max(1, 2, key=int, abc=int)", # two many keywords
@ -865,6 +876,13 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(max((1,2), key=neg), 1) # two elem iterable self.assertEqual(max((1,2), key=neg), 1) # two elem iterable
self.assertEqual(max(1, 2, key=neg), 1) # two elems 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=3, key=neg), 1)
data = [random.randrange(200) for i in range(100)] data = [random.randrange(200) for i in range(100)]
keys = dict((elem, random.randrange(50)) for elem in data) keys = dict((elem, random.randrange(50)) for elem in data)
f = keys.__getitem__ f = keys.__getitem__
@ -891,6 +909,9 @@ class BuiltinTest(unittest.TestCase):
for stmt in ( for stmt in (
"min(key=int)", # no args "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, key=int)", # single arg not iterable
"min(1, 2, keystone=int)", # wrong keyword "min(1, 2, keystone=int)", # wrong keyword
"min(1, 2, key=int, abc=int)", # two many keywords "min(1, 2, key=int, abc=int)", # two many keywords
@ -907,6 +928,13 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(min((1,2), key=neg), 2) # two elem iterable self.assertEqual(min((1,2), key=neg), 2) # two elem iterable
self.assertEqual(min(1, 2, key=neg), 2) # two elems 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)] data = [random.randrange(200) for i in range(100)]
keys = dict((elem, random.randrange(50)) for elem in data) keys = dict((elem, random.randrange(50)) for elem in data)
f = keys.__getitem__ f = keys.__getitem__

View File

@ -13,6 +13,10 @@ Core and Builtins
- Issue #18184: PyUnicode_FromFormat() and PyUnicode_FromFormatV() now raise - Issue #18184: PyUnicode_FromFormat() and PyUnicode_FromFormatV() now raise
OverflowError when an argument of %c format is out of range. OverflowError when an argument of %c format is out of range.
- Issue #18111: The min() and max() functions now support a default argument
to be returned instead of raising a ValueError on an empty sequence.
(Contributed by Julian Berman.)
- Issue #18137: Detect integer overflow on precision in float.__format__() - Issue #18137: Detect integer overflow on precision in float.__format__()
and complex.__format__(). and complex.__format__().

View File

@ -1329,26 +1329,35 @@ static PyObject *
min_max(PyObject *args, PyObject *kwds, int op) min_max(PyObject *args, PyObject *kwds, int op)
{ {
PyObject *v, *it, *item, *val, *maxitem, *maxval, *keyfunc=NULL; 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"; const char *name = op == Py_LT ? "min" : "max";
const int positional = PyTuple_Size(args) > 1;
int ret;
if (PyTuple_Size(args) > 1) if (positional)
v = args; v = args;
else if (!PyArg_UnpackTuple(args, (char *)name, 1, 1, &v)) else if (!PyArg_UnpackTuple(args, (char *)name, 1, 1, &v))
return NULL; return NULL;
if (kwds != NULL && PyDict_Check(kwds) && PyDict_Size(kwds)) { emptytuple = PyTuple_New(0);
keyfunc = PyDict_GetItemString(kwds, "key"); if (emptytuple == NULL)
if (PyDict_Size(kwds)!=1 || keyfunc == NULL) { return NULL;
PyErr_Format(PyExc_TypeError, ret = PyArg_ParseTupleAndKeywords(emptytuple, kwds, "|$OO", kwlist,
"%s() got an unexpected keyword argument", name); &keyfunc, &defaultval);
return NULL; Py_DECREF(emptytuple);
} if (!ret)
Py_INCREF(keyfunc); 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); it = PyObject_GetIter(v);
if (it == NULL) { if (it == NULL) {
Py_XDECREF(keyfunc);
return NULL; return NULL;
} }
@ -1392,14 +1401,18 @@ min_max(PyObject *args, PyObject *kwds, int op)
if (PyErr_Occurred()) if (PyErr_Occurred())
goto Fail_it; goto Fail_it;
if (maxval == NULL) { if (maxval == NULL) {
PyErr_Format(PyExc_ValueError,
"%s() arg is an empty sequence", name);
assert(maxitem == NULL); assert(maxitem == NULL);
if (defaultval != NULL) {
Py_INCREF(defaultval);
maxitem = defaultval;
} else {
PyErr_Format(PyExc_ValueError,
"%s() arg is an empty sequence", name);
}
} }
else else
Py_DECREF(maxval); Py_DECREF(maxval);
Py_DECREF(it); Py_DECREF(it);
Py_XDECREF(keyfunc);
return maxitem; return maxitem;
Fail_it_item_and_val: Fail_it_item_and_val:
@ -1410,7 +1423,6 @@ Fail_it:
Py_XDECREF(maxval); Py_XDECREF(maxval);
Py_XDECREF(maxitem); Py_XDECREF(maxitem);
Py_DECREF(it); Py_DECREF(it);
Py_XDECREF(keyfunc);
return NULL; return NULL;
} }