mirror of
https://github.com/python/cpython.git
synced 2025-07-23 19:25:40 +00:00
SF patch #1077353: add key= argument to min and max
(First draft of patch contributed by Steven Bethard.)
This commit is contained in:
parent
e8fdc4502f
commit
3b0c7c20a1
5 changed files with 164 additions and 45 deletions
|
@ -642,16 +642,28 @@ class C:
|
||||||
of sequence; the result is always a list.
|
of sequence; the result is always a list.
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
\begin{funcdesc}{max}{s\optional{, args...}}
|
\begin{funcdesc}{max}{s\optional{, args...}\optional{key}}
|
||||||
With a single argument \var{s}, return the largest item of a
|
With a single argument \var{s}, return the largest item of a
|
||||||
non-empty sequence (such as a string, tuple or list). With more
|
non-empty sequence (such as a string, tuple or list). With more
|
||||||
than one argument, return the largest of the arguments.
|
than one argument, return the largest of the arguments.
|
||||||
|
|
||||||
|
The optional \var{key} argument specifies a one argument ordering
|
||||||
|
function like that used for \method{list.sort()}. The \var{key}
|
||||||
|
argument, if supplied, must be in keyword form (for example,
|
||||||
|
\samp{max(a,b,c,key=func)}).
|
||||||
|
\versionchanged[Added support for the optional \var{key} argument]{2.5}
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
\begin{funcdesc}{min}{s\optional{, args...}}
|
\begin{funcdesc}{min}{s\optional{, args...}}
|
||||||
With a single argument \var{s}, return the smallest item of a
|
With a single argument \var{s}, return the smallest item of a
|
||||||
non-empty sequence (such as a string, tuple or list). With more
|
non-empty sequence (such as a string, tuple or list). With more
|
||||||
than one argument, return the smallest of the arguments.
|
than one argument, return the smallest of the arguments.
|
||||||
|
|
||||||
|
The optional \var{key} argument specifies a one argument ordering
|
||||||
|
function like that used for \method{list.sort()}. The \var{key}
|
||||||
|
argument, if supplied, must be in keyword form (for example,
|
||||||
|
\samp{min(a,b,c,key=func)}).
|
||||||
|
\versionchanged[Added support for the optional \var{key} argument]{2.5}
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
\begin{funcdesc}{object}{}
|
\begin{funcdesc}{object}{}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# Python test set -- built-in functions
|
# Python test set -- built-in functions
|
||||||
|
|
||||||
import test.test_support, unittest
|
import test.test_support, unittest
|
||||||
from test.test_support import fcmp, have_unicode, TESTFN, unlink
|
from test.test_support import fcmp, have_unicode, TESTFN, unlink, run_unittest
|
||||||
|
from operator import neg
|
||||||
|
|
||||||
import sys, warnings, cStringIO, random, UserDict
|
import sys, warnings, cStringIO, random, UserDict
|
||||||
warnings.filterwarnings("ignore", "hex../oct.. of negative int",
|
warnings.filterwarnings("ignore", "hex../oct.. of negative int",
|
||||||
|
@ -9,6 +10,10 @@ warnings.filterwarnings("ignore", "hex../oct.. of negative int",
|
||||||
warnings.filterwarnings("ignore", "integer argument expected",
|
warnings.filterwarnings("ignore", "integer argument expected",
|
||||||
DeprecationWarning, "unittest")
|
DeprecationWarning, "unittest")
|
||||||
|
|
||||||
|
# count the number of test runs.
|
||||||
|
# used to skip running test_execfile() multiple times
|
||||||
|
numruns = 0
|
||||||
|
|
||||||
class Squares:
|
class Squares:
|
||||||
|
|
||||||
def __init__(self, max):
|
def __init__(self, max):
|
||||||
|
@ -343,6 +348,11 @@ class BuiltinTest(unittest.TestCase):
|
||||||
execfile(TESTFN)
|
execfile(TESTFN)
|
||||||
|
|
||||||
def test_execfile(self):
|
def test_execfile(self):
|
||||||
|
global numruns
|
||||||
|
if numruns:
|
||||||
|
return
|
||||||
|
numruns += 1
|
||||||
|
|
||||||
globals = {'a': 1, 'b': 2}
|
globals = {'a': 1, 'b': 2}
|
||||||
locals = {'b': 200, 'c': 300}
|
locals = {'b': 200, 'c': 300}
|
||||||
|
|
||||||
|
@ -845,6 +855,30 @@ class BuiltinTest(unittest.TestCase):
|
||||||
self.assertEqual(max(1L, 2.0, 3), 3)
|
self.assertEqual(max(1L, 2.0, 3), 3)
|
||||||
self.assertEqual(max(1.0, 2, 3L), 3L)
|
self.assertEqual(max(1.0, 2, 3L), 3L)
|
||||||
|
|
||||||
|
for stmt in (
|
||||||
|
"max(key=int)", # no args
|
||||||
|
"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
|
||||||
|
"max(1, 2, key=1)", # keyfunc is not callable
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
exec(stmt) in globals()
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fail(stmt)
|
||||||
|
|
||||||
|
self.assertEqual(max((1,), key=neg), 1) # one elem iterable
|
||||||
|
self.assertEqual(max((1,2), key=neg), 1) # two elem iterable
|
||||||
|
self.assertEqual(max(1, 2, key=neg), 1) # two elems
|
||||||
|
|
||||||
|
data = [random.randrange(200) for i in range(100)]
|
||||||
|
keys = dict((elem, random.randrange(50)) for elem in data)
|
||||||
|
f = keys.__getitem__
|
||||||
|
self.assertEqual(max(data, key=f),
|
||||||
|
sorted(reversed(data), key=f)[-1])
|
||||||
|
|
||||||
def test_min(self):
|
def test_min(self):
|
||||||
self.assertEqual(min('123123'), '1')
|
self.assertEqual(min('123123'), '1')
|
||||||
self.assertEqual(min(1, 2, 3), 1)
|
self.assertEqual(min(1, 2, 3), 1)
|
||||||
|
@ -867,6 +901,30 @@ class BuiltinTest(unittest.TestCase):
|
||||||
raise ValueError
|
raise ValueError
|
||||||
self.assertRaises(ValueError, min, (42, BadNumber()))
|
self.assertRaises(ValueError, min, (42, BadNumber()))
|
||||||
|
|
||||||
|
for stmt in (
|
||||||
|
"min(key=int)", # no args
|
||||||
|
"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
|
||||||
|
"min(1, 2, key=1)", # keyfunc is not callable
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
exec(stmt) in globals()
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fail(stmt)
|
||||||
|
|
||||||
|
self.assertEqual(min((1,), key=neg), 1) # one elem iterable
|
||||||
|
self.assertEqual(min((1,2), key=neg), 2) # two elem iterable
|
||||||
|
self.assertEqual(min(1, 2, key=neg), 2) # two elems
|
||||||
|
|
||||||
|
data = [random.randrange(200) for i in range(100)]
|
||||||
|
keys = dict((elem, random.randrange(50)) for elem in data)
|
||||||
|
f = keys.__getitem__
|
||||||
|
self.assertEqual(min(data, key=f),
|
||||||
|
sorted(data, key=f)[0])
|
||||||
|
|
||||||
def test_oct(self):
|
def test_oct(self):
|
||||||
self.assertEqual(oct(100), '0144')
|
self.assertEqual(oct(100), '0144')
|
||||||
self.assertEqual(oct(100L), '0144L')
|
self.assertEqual(oct(100L), '0144L')
|
||||||
|
@ -1313,8 +1371,21 @@ class TestSorted(unittest.TestCase):
|
||||||
data = 'The quick Brown fox Jumped over The lazy Dog'.split()
|
data = 'The quick Brown fox Jumped over The lazy Dog'.split()
|
||||||
self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0)
|
self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0)
|
||||||
|
|
||||||
def test_main():
|
def test_main(verbose=None):
|
||||||
test.test_support.run_unittest(BuiltinTest, TestSorted)
|
test_classes = (BuiltinTest, TestSorted)
|
||||||
|
|
||||||
|
run_unittest(*test_classes)
|
||||||
|
|
||||||
|
# verify reference counting
|
||||||
|
if verbose and hasattr(sys, "gettotalrefcount"):
|
||||||
|
import gc
|
||||||
|
counts = [None] * 5
|
||||||
|
for i in xrange(len(counts)):
|
||||||
|
run_unittest(*test_classes)
|
||||||
|
gc.collect()
|
||||||
|
counts[i] = sys.gettotalrefcount()
|
||||||
|
print counts
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main(verbose=True)
|
||||||
|
|
|
@ -52,6 +52,7 @@ Alexander Belopolsky
|
||||||
Andy Bensky
|
Andy Bensky
|
||||||
Michel Van den Bergh
|
Michel Van den Bergh
|
||||||
Eric Beser
|
Eric Beser
|
||||||
|
Steven Bethard
|
||||||
Stephen Bevan
|
Stephen Bevan
|
||||||
Ron Bickers
|
Ron Bickers
|
||||||
Dominic Binks
|
Dominic Binks
|
||||||
|
|
|
@ -10,6 +10,9 @@ What's New in Python 2.5 alpha 1?
|
||||||
Core and builtins
|
Core and builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- min() and max() now support key= arguments with the same meaning as in
|
||||||
|
list.sort().
|
||||||
|
|
||||||
|
|
||||||
Extension Modules
|
Extension Modules
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -19,7 +22,7 @@ Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
- heapq.nsmallest() and heapq.nlargest() now support key= arguments with
|
- heapq.nsmallest() and heapq.nlargest() now support key= arguments with
|
||||||
the same meaning as for list.sort().
|
the same meaning as in list.sort().
|
||||||
|
|
||||||
|
|
||||||
Build
|
Build
|
||||||
|
|
|
@ -1114,82 +1114,114 @@ Update and return a dictionary containing the current scope's local variables.")
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
min_max(PyObject *args, int op)
|
min_max(PyObject *args, PyObject *kwds, int op)
|
||||||
{
|
{
|
||||||
|
PyObject *v, *it, *item, *val, *maxitem, *maxval, *keyfunc=NULL;
|
||||||
const char *name = op == Py_LT ? "min" : "max";
|
const char *name = op == Py_LT ? "min" : "max";
|
||||||
PyObject *v, *w, *x, *it;
|
|
||||||
|
|
||||||
if (PyTuple_Size(args) > 1)
|
if (PyTuple_Size(args) > 1)
|
||||||
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)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
it = PyObject_GetIter(v);
|
it = PyObject_GetIter(v);
|
||||||
if (it == NULL)
|
if (it == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
w = NULL; /* the result */
|
maxitem = NULL; /* the result */
|
||||||
for (;;) {
|
maxval = NULL; /* the value associated with the result */
|
||||||
x = PyIter_Next(it);
|
while (item = PyIter_Next(it)) {
|
||||||
if (x == NULL) {
|
/* get the value from the key function */
|
||||||
if (PyErr_Occurred()) {
|
if (keyfunc != NULL) {
|
||||||
Py_XDECREF(w);
|
val = PyObject_CallFunctionObjArgs(keyfunc, item, NULL);
|
||||||
Py_DECREF(it);
|
if (val == NULL)
|
||||||
return NULL;
|
goto Fail_it_item;
|
||||||
}
|
}
|
||||||
break;
|
/* no key function; the value is the item */
|
||||||
|
else {
|
||||||
|
val = item;
|
||||||
|
Py_INCREF(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (w == NULL)
|
/* maximum value and item are unset; set them */
|
||||||
w = x;
|
if (maxval == NULL) {
|
||||||
|
maxitem = item;
|
||||||
|
maxval = val;
|
||||||
|
}
|
||||||
|
/* maximum value and item are set; update them as necessary */
|
||||||
else {
|
else {
|
||||||
int cmp = PyObject_RichCompareBool(x, w, op);
|
int cmp = PyObject_RichCompareBool(val, maxval, op);
|
||||||
if (cmp > 0) {
|
if (cmp < 0)
|
||||||
Py_DECREF(w);
|
goto Fail_it_item_and_val;
|
||||||
w = x;
|
else if (cmp > 0) {
|
||||||
|
Py_DECREF(maxval);
|
||||||
|
Py_DECREF(maxitem);
|
||||||
|
maxval = val;
|
||||||
|
maxitem = item;
|
||||||
}
|
}
|
||||||
else if (cmp < 0) {
|
else {
|
||||||
Py_DECREF(x);
|
Py_DECREF(item);
|
||||||
Py_DECREF(w);
|
Py_DECREF(val);
|
||||||
Py_DECREF(it);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
Py_DECREF(x);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (w == NULL)
|
if (PyErr_Occurred())
|
||||||
|
goto Fail_it;
|
||||||
|
if (maxval == NULL) {
|
||||||
PyErr_Format(PyExc_ValueError,
|
PyErr_Format(PyExc_ValueError,
|
||||||
"%s() arg is an empty sequence", name);
|
"%s() arg is an empty sequence", name);
|
||||||
|
assert(maxitem == NULL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Py_DECREF(maxval);
|
||||||
Py_DECREF(it);
|
Py_DECREF(it);
|
||||||
return w;
|
return maxitem;
|
||||||
|
|
||||||
|
Fail_it_item_and_val:
|
||||||
|
Py_DECREF(val);
|
||||||
|
Fail_it_item:
|
||||||
|
Py_DECREF(item);
|
||||||
|
Fail_it:
|
||||||
|
Py_XDECREF(maxval);
|
||||||
|
Py_XDECREF(maxitem);
|
||||||
|
Py_DECREF(it);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
builtin_min(PyObject *self, PyObject *v)
|
builtin_min(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
return min_max(v, Py_LT);
|
return min_max(args, kwds, Py_LT);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(min_doc,
|
PyDoc_STRVAR(min_doc,
|
||||||
"min(sequence) -> value\n\
|
"min(iterable[, key=func]) -> value\n\
|
||||||
min(a, b, c, ...) -> value\n\
|
min(a, b, c, ...[, key=func]) -> value\n\
|
||||||
\n\
|
\n\
|
||||||
With a single sequence argument, return its smallest item.\n\
|
With a single iterable argument, return its smallest item.\n\
|
||||||
With two or more arguments, return the smallest argument.");
|
With two or more arguments, return the smallest argument.");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
builtin_max(PyObject *self, PyObject *v)
|
builtin_max(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
return min_max(v, Py_GT);
|
return min_max(args, kwds, Py_GT);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(max_doc,
|
PyDoc_STRVAR(max_doc,
|
||||||
"max(sequence) -> value\n\
|
"max(iterable[, key=func]) -> value\n\
|
||||||
max(a, b, c, ...) -> value\n\
|
max(a, b, c, ...[, key=func]) -> value\n\
|
||||||
\n\
|
\n\
|
||||||
With a single sequence argument, return its largest item.\n\
|
With a single iterable argument, return its largest item.\n\
|
||||||
With two or more arguments, return the largest argument.");
|
With two or more arguments, return the largest argument.");
|
||||||
|
|
||||||
|
|
||||||
|
@ -2119,8 +2151,8 @@ static PyMethodDef builtin_methods[] = {
|
||||||
{"len", builtin_len, METH_O, len_doc},
|
{"len", builtin_len, METH_O, len_doc},
|
||||||
{"locals", (PyCFunction)builtin_locals, METH_NOARGS, locals_doc},
|
{"locals", (PyCFunction)builtin_locals, METH_NOARGS, locals_doc},
|
||||||
{"map", builtin_map, METH_VARARGS, map_doc},
|
{"map", builtin_map, METH_VARARGS, map_doc},
|
||||||
{"max", builtin_max, METH_VARARGS, max_doc},
|
{"max", (PyCFunction)builtin_max, METH_VARARGS | METH_KEYWORDS, max_doc},
|
||||||
{"min", builtin_min, METH_VARARGS, min_doc},
|
{"min", (PyCFunction)builtin_min, METH_VARARGS | METH_KEYWORDS, min_doc},
|
||||||
{"oct", builtin_oct, METH_O, oct_doc},
|
{"oct", builtin_oct, METH_O, oct_doc},
|
||||||
{"ord", builtin_ord, METH_O, ord_doc},
|
{"ord", builtin_ord, METH_O, ord_doc},
|
||||||
{"pow", builtin_pow, METH_VARARGS, pow_doc},
|
{"pow", builtin_pow, METH_VARARGS, pow_doc},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue