Issue #1771: Remove cmp parameter from list.sort() and builtin.sorted().

This commit is contained in:
Raymond Hettinger 2008-01-30 20:15:17 +00:00
parent 4f066126d6
commit 70b64fce96
10 changed files with 82 additions and 265 deletions

View file

@ -959,31 +959,20 @@ available. They are listed here in alphabetical order.
``a[start:stop:step]`` or ``a[start:stop, i]``. ``a[start:stop:step]`` or ``a[start:stop, i]``.
.. function:: sorted(iterable[, cmp[, key[, reverse]]]) .. function:: sorted(iterable[, key[, reverse]])
Return a new sorted list from the items in *iterable*. Return a new sorted list from the items in *iterable*.
The optional arguments *cmp*, *key*, and *reverse* have the same meaning as The optional arguments *key* and *reverse* have the same meaning as
those for the :meth:`list.sort` method (described in section those for the :meth:`list.sort` method (described in section
:ref:`typesseq-mutable`). :ref:`typesseq-mutable`).
*cmp* specifies a custom comparison function of two arguments (iterable
elements) which should return a negative, zero or positive number depending on
whether the first argument is considered smaller than, equal to, or larger than
the second argument: ``cmp=lambda x,y: cmp(x.lower(), y.lower())``. The default
value is ``None``.
*key* specifies a function of one argument that is used to extract a comparison *key* specifies a function of one argument that is used to extract a comparison
key from each list element: ``key=str.lower``. The default value is ``None``. key from each list element: ``key=str.lower``. The default value is ``None``.
*reverse* is a boolean value. If set to ``True``, then the list elements are *reverse* is a boolean value. If set to ``True``, then the list elements are
sorted as if each comparison were reversed. sorted as if each comparison were reversed.
In general, the *key* and *reverse* conversion processes are much faster than
specifying an equivalent *cmp* function. This is because *cmp* is called
multiple times for each list element while *key* and *reverse* touch each
element only once.
.. function:: staticmethod(function) .. function:: staticmethod(function)

View file

@ -1266,8 +1266,7 @@ Note that while lists allow their items to be of any type, bytearray object
| ``s.reverse()`` | reverses the items of *s* in | \(6) | | ``s.reverse()`` | reverses the items of *s* in | \(6) |
| | place | | | | place | |
+------------------------------+--------------------------------+---------------------+ +------------------------------+--------------------------------+---------------------+
| ``s.sort([cmp[, key[, | sort the items of *s* in place | (6), (7) | | ``s.sort([key[, reverse]])`` | sort the items of *s* in place | (6), (7) |
| reverse]]])`` | | |
+------------------------------+--------------------------------+---------------------+ +------------------------------+--------------------------------+---------------------+
.. index:: .. index::
@ -1321,23 +1320,12 @@ Notes:
The :meth:`sort` method takes optional arguments for controlling the The :meth:`sort` method takes optional arguments for controlling the
comparisons. comparisons.
*cmp* specifies a custom comparison function of two arguments (list items) which
should return a negative, zero or positive number depending on whether the first
argument is considered smaller than, equal to, or larger than the second
argument: ``cmp=lambda x,y: cmp(x.lower(), y.lower())``. The default value
is ``None``.
*key* specifies a function of one argument that is used to extract a comparison *key* specifies a function of one argument that is used to extract a comparison
key from each list element: ``key=str.lower``. The default value is ``None``. key from each list element: ``key=str.lower``. The default value is ``None``.
*reverse* is a boolean value. If set to ``True``, then the list elements are *reverse* is a boolean value. If set to ``True``, then the list elements are
sorted as if each comparison were reversed. sorted as if each comparison were reversed.
In general, the *key* and *reverse* conversion processes are much faster than
specifying an equivalent *cmp* function. This is because *cmp* is called
multiple times for each list element while *key* and *reverse* touch each
element only once.
Starting with Python 2.3, the :meth:`sort` method is guaranteed to be stable. A Starting with Python 2.3, the :meth:`sort` method is guaranteed to be stable. A
sort is stable if it guarantees not to change the relative order of elements sort is stable if it guarantees not to change the relative order of elements
that compare equal --- this is helpful for sorting in multiple passes (for that compare equal --- this is helpful for sorting in multiple passes (for

View file

@ -1255,8 +1255,7 @@ class CookieJar:
""" """
# add cookies in order of most specific (ie. longest) path first # add cookies in order of most specific (ie. longest) path first
def decreasing_size(a, b): return cmp(len(b.path), len(a.path)) cookies.sort(key=lambda a: len(a.path), reverse=True)
cookies.sort(decreasing_size)
version_set = False version_set = False

View file

@ -238,7 +238,7 @@ class Stats:
stats_list.append((cc, nc, tt, ct) + func + stats_list.append((cc, nc, tt, ct) + func +
(func_std_string(func), func)) (func_std_string(func), func))
stats_list.sort(TupleComp(sort_tuple).compare) stats_list.sort(key=CmpToKey(TupleComp(sort_tuple).compare))
self.fcn_list = fcn_list = [] self.fcn_list = fcn_list = []
for tuple in stats_list: for tuple in stats_list:
@ -470,6 +470,16 @@ class TupleComp:
return direction return direction
return 0 return 0
def CmpToKey(mycmp):
'Convert a cmp= function into a key= function'
class K(object):
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) == -1
return K
#************************************************************************** #**************************************************************************
# func_name is a triple (file:string, line:int, name:string) # func_name is a triple (file:string, line:int, name:string)

View file

@ -8,6 +8,15 @@ import os
import unittest import unittest
from test import test_support, seq_tests from test import test_support, seq_tests
def CmpToKey(mycmp):
'Convert a cmp= function into a key= function'
class K(object):
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) == -1
return K
class CommonTest(seq_tests.CommonTest): class CommonTest(seq_tests.CommonTest):
def test_init(self): def test_init(self):
@ -430,23 +439,21 @@ class CommonTest(seq_tests.CommonTest):
def revcmp(a, b): def revcmp(a, b):
return cmp(b, a) return cmp(b, a)
u.sort(revcmp) u.sort(key=CmpToKey(revcmp))
self.assertEqual(u, self.type2test([2,1,0,-1,-2])) self.assertEqual(u, self.type2test([2,1,0,-1,-2]))
# The following dumps core in unpatched Python 1.5: # The following dumps core in unpatched Python 1.5:
def myComparison(x,y): def myComparison(x,y):
return cmp(x%3, y%7) return cmp(x%3, y%7)
z = self.type2test(range(12)) z = self.type2test(range(12))
z.sort(myComparison) z.sort(key=CmpToKey(myComparison))
self.assertRaises(TypeError, z.sort, 2) self.assertRaises(TypeError, z.sort, 2)
def selfmodifyingComparison(x,y): def selfmodifyingComparison(x,y):
z.append(1) z.append(1)
return cmp(x, y) return cmp(x, y)
self.assertRaises(ValueError, z.sort, selfmodifyingComparison) self.assertRaises(ValueError, z.sort, key=CmpToKey(selfmodifyingComparison))
self.assertRaises(TypeError, z.sort, lambda x, y: 's')
self.assertRaises(TypeError, z.sort, 42, 42, 42, 42) self.assertRaises(TypeError, z.sort, 42, 42, 42, 42)

View file

@ -1764,9 +1764,6 @@ class TestSorted(unittest.TestCase):
data.reverse() data.reverse()
random.shuffle(copy) random.shuffle(copy)
self.assertEqual(data, sorted(copy, cmp=lambda x, y: (x < y) - (x > y)))
self.assertNotEqual(data, copy)
random.shuffle(copy)
self.assertEqual(data, sorted(copy, key=lambda x: -x)) self.assertEqual(data, sorted(copy, key=lambda x: -x))
self.assertNotEqual(data, copy) self.assertNotEqual(data, copy)
random.shuffle(copy) random.shuffle(copy)

View file

@ -6,6 +6,15 @@ import unittest
verbose = test_support.verbose verbose = test_support.verbose
nerrors = 0 nerrors = 0
def CmpToKey(mycmp):
'Convert a cmp= function into a key= function'
class K(object):
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) == -1
return K
def check(tag, expected, raw, compare=None): def check(tag, expected, raw, compare=None):
global nerrors global nerrors
@ -14,7 +23,7 @@ def check(tag, expected, raw, compare=None):
orig = raw[:] # save input in case of error orig = raw[:] # save input in case of error
if compare: if compare:
raw.sort(compare) raw.sort(key=CmpToKey(compare))
else: else:
raw.sort() raw.sort()
@ -99,7 +108,7 @@ class TestBase(unittest.TestCase):
print(" Checking against an insane comparison function.") print(" Checking against an insane comparison function.")
print(" If the implementation isn't careful, this may segfault.") print(" If the implementation isn't careful, this may segfault.")
s = x[:] s = x[:]
s.sort(lambda a, b: int(random.random() * 3) - 1) s.sort(key=CmpToKey(lambda a, b: int(random.random() * 3) - 1))
check("an insane function left some permutation", x, s) check("an insane function left some permutation", x, s)
x = [Complains(i) for i in x] x = [Complains(i) for i in x]
@ -141,14 +150,6 @@ class TestBugs(unittest.TestCase):
L = [C() for i in range(50)] L = [C() for i in range(50)]
self.assertRaises(ValueError, L.sort) self.assertRaises(ValueError, L.sort)
def test_cmpNone(self):
# Testing None as a comparison function.
L = list(range(50))
random.shuffle(L)
L.sort(None)
self.assertEqual(L, list(range(50)))
def test_undetected_mutation(self): def test_undetected_mutation(self):
# Python 2.4a1 did not always detect mutation # Python 2.4a1 did not always detect mutation
memorywaster = [] memorywaster = []
@ -158,12 +159,12 @@ class TestBugs(unittest.TestCase):
L.pop() L.pop()
return cmp(x, y) return cmp(x, y)
L = [1,2] L = [1,2]
self.assertRaises(ValueError, L.sort, mutating_cmp) self.assertRaises(ValueError, L.sort, key=CmpToKey(mutating_cmp))
def mutating_cmp(x, y): def mutating_cmp(x, y):
L.append(3) L.append(3)
del L[:] del L[:]
return cmp(x, y) return cmp(x, y)
self.assertRaises(ValueError, L.sort, mutating_cmp) self.assertRaises(ValueError, L.sort, key=CmpToKey(mutating_cmp))
memorywaster = [memorywaster] memorywaster = [memorywaster]
#============================================================================== #==============================================================================
@ -175,11 +176,11 @@ class TestDecorateSortUndecorate(unittest.TestCase):
copy = data[:] copy = data[:]
random.shuffle(data) random.shuffle(data)
data.sort(key=str.lower) data.sort(key=str.lower)
copy.sort(cmp=lambda x,y: cmp(x.lower(), y.lower())) copy.sort(key=CmpToKey(lambda x,y: cmp(x.lower(), y.lower())))
def test_baddecorator(self): def test_baddecorator(self):
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, data.sort, None, lambda x,y: 0) self.assertRaises(TypeError, data.sort, key=lambda x,y: 0)
def test_stability(self): def test_stability(self):
data = [(random.randrange(100), i) for i in range(200)] data = [(random.randrange(100), i) for i in range(200)]
@ -188,25 +189,11 @@ class TestDecorateSortUndecorate(unittest.TestCase):
copy.sort() # sort using both fields copy.sort() # sort using both fields
self.assertEqual(data, copy) # should get the same result self.assertEqual(data, copy) # should get the same result
def test_cmp_and_key_combination(self):
# Verify that the wrapper has been removed
def compare(x, y):
self.assertEqual(type(x), str)
self.assertEqual(type(x), str)
return cmp(x, y)
data = 'The quick Brown fox Jumped over The lazy Dog'.split()
data.sort(cmp=compare, key=str.lower)
def test_badcmp_with_key(self):
# Verify that the wrapper has been removed
data = 'The quick Brown fox Jumped over The lazy Dog'.split()
self.assertRaises(TypeError, data.sort, "bad", str.lower)
def test_key_with_exception(self): def test_key_with_exception(self):
# Verify that the wrapper has been removed # Verify that the wrapper has been removed
data = list(range(-2, 2)) data = list(range(-2, 2))
dup = data[:] dup = data[:]
self.assertRaises(ZeroDivisionError, data.sort, None, lambda x: 1/x) self.assertRaises(ZeroDivisionError, data.sort, key=lambda x: 1/x)
self.assertEqual(data, dup) self.assertEqual(data, dup)
def test_key_with_mutation(self): def test_key_with_mutation(self):
@ -254,14 +241,13 @@ class TestDecorateSortUndecorate(unittest.TestCase):
random.shuffle(data) random.shuffle(data)
data.sort(reverse=True) data.sort(reverse=True)
self.assertEqual(data, list(range(99,-1,-1))) self.assertEqual(data, list(range(99,-1,-1)))
self.assertRaises(TypeError, data.sort, "wrong type")
def test_reverse_stability(self): def test_reverse_stability(self):
data = [(random.randrange(100), i) for i in range(200)] data = [(random.randrange(100), i) for i in range(200)]
copy1 = data[:] copy1 = data[:]
copy2 = data[:] copy2 = data[:]
data.sort(cmp=lambda x,y: cmp(x[0],y[0]), reverse=True) data.sort(key=CmpToKey(lambda x,y: cmp(x[0],y[0])), reverse=True)
copy1.sort(cmp=lambda x,y: cmp(y[0],x[0])) copy1.sort(key=CmpToKey(lambda x,y: cmp(y[0],x[0])))
self.assertEqual(data, copy1) self.assertEqual(data, copy1)
copy2.sort(key=lambda x: x[0], reverse=True) copy2.sort(key=lambda x: x[0], reverse=True)
self.assertEqual(data, copy2) self.assertEqual(data, copy2)

View file

@ -14,6 +14,8 @@ Core and Builtins
- Issue #1973: bytes.fromhex('') raises SystemError - Issue #1973: bytes.fromhex('') raises SystemError
- Issue #1771: remove cmp parameter from sorted() and list.sort()
- Issue #1969: split and rsplit in bytearray are inconsistent - Issue #1969: split and rsplit in bytearray are inconsistent
- map() and itertools.imap() no longer accept None for the first argument. - map() and itertools.imap() no longer accept None for the first argument.

View file

@ -888,64 +888,17 @@ reverse_slice(PyObject **lo, PyObject **hi)
* pieces to this algorithm; read listsort.txt for overviews and details. * pieces to this algorithm; read listsort.txt for overviews and details.
*/ */
/* Comparison function. Takes care of calling a user-supplied /* Comparison function: PyObject_RichCompareBool with Py_LT.
* comparison function (any callable Python object), which must not be
* NULL (use the ISLT macro if you don't know, or call PyObject_RichCompareBool
* with Py_LT if you know it's NULL).
* Returns -1 on error, 1 if x < y, 0 if x >= y. * Returns -1 on error, 1 if x < y, 0 if x >= y.
*/ */
static int
islt(PyObject *x, PyObject *y, PyObject *compare)
{
PyObject *res;
PyObject *args;
Py_ssize_t i;
assert(compare != NULL); #define ISLT(X, Y) (PyObject_RichCompareBool(X, Y, Py_LT))
/* Call the user's comparison function and translate the 3-way
* result into true or false (or error).
*/
args = PyTuple_New(2);
if (args == NULL)
return -1;
Py_INCREF(x);
Py_INCREF(y);
PyTuple_SET_ITEM(args, 0, x);
PyTuple_SET_ITEM(args, 1, y);
res = PyObject_Call(compare, args, NULL);
Py_DECREF(args);
if (res == NULL)
return -1;
if (!PyLong_CheckExact(res)) {
PyErr_Format(PyExc_TypeError,
"comparison function must return int, not %.200s",
res->ob_type->tp_name);
Py_DECREF(res);
return -1;
}
i = PyLong_AsLong(res);
Py_DECREF(res);
if (i == -1 && PyErr_Occurred()) {
/* Overflow in long conversion. */
return -1;
}
return i < 0;
}
/* If COMPARE is NULL, calls PyObject_RichCompareBool with Py_LT, else calls
* islt. This avoids a layer of function call in the usual case, and
* sorting does many comparisons.
* Returns -1 on error, 1 if x < y, 0 if x >= y.
*/
#define ISLT(X, Y, COMPARE) ((COMPARE) == NULL ? \
PyObject_RichCompareBool(X, Y, Py_LT) : \
islt(X, Y, COMPARE))
/* Compare X to Y via "<". Goto "fail" if the comparison raises an /* Compare X to Y via "<". Goto "fail" if the comparison raises an
error. Else "k" is set to true iff X<Y, and an "if (k)" block is error. Else "k" is set to true iff X<Y, and an "if (k)" block is
started. It makes more sense in context <wink>. X and Y are PyObject*s. started. It makes more sense in context <wink>. X and Y are PyObject*s.
*/ */
#define IFLT(X, Y) if ((k = ISLT(X, Y, compare)) < 0) goto fail; \ #define IFLT(X, Y) if ((k = ISLT(X, Y)) < 0) goto fail; \
if (k) if (k)
/* binarysort is the best method for sorting small arrays: it does /* binarysort is the best method for sorting small arrays: it does
@ -960,8 +913,7 @@ islt(PyObject *x, PyObject *y, PyObject *compare)
the input (nothing is lost or duplicated). the input (nothing is lost or duplicated).
*/ */
static int static int
binarysort(PyObject **lo, PyObject **hi, PyObject **start, PyObject *compare) binarysort(PyObject **lo, PyObject **hi, PyObject **start)
/* compare -- comparison function object, or NULL for default */
{ {
register Py_ssize_t k; register Py_ssize_t k;
register PyObject **l, **p, **r; register PyObject **l, **p, **r;
@ -1026,7 +978,7 @@ elements to get out of order).
Returns -1 in case of error. Returns -1 in case of error.
*/ */
static Py_ssize_t static Py_ssize_t
count_run(PyObject **lo, PyObject **hi, PyObject *compare, int *descending) count_run(PyObject **lo, PyObject **hi, int *descending)
{ {
Py_ssize_t k; Py_ssize_t k;
Py_ssize_t n; Py_ssize_t n;
@ -1081,7 +1033,7 @@ key, and the last n-k should follow key.
Returns -1 on error. See listsort.txt for info on the method. Returns -1 on error. See listsort.txt for info on the method.
*/ */
static Py_ssize_t static Py_ssize_t
gallop_left(PyObject *key, PyObject **a, Py_ssize_t n, Py_ssize_t hint, PyObject *compare) gallop_left(PyObject *key, PyObject **a, Py_ssize_t n, Py_ssize_t hint)
{ {
Py_ssize_t ofs; Py_ssize_t ofs;
Py_ssize_t lastofs; Py_ssize_t lastofs;
@ -1172,7 +1124,7 @@ we're sticking to "<" comparisons that it's much harder to follow if
written as one routine with yet another "left or right?" flag. written as one routine with yet another "left or right?" flag.
*/ */
static Py_ssize_t static Py_ssize_t
gallop_right(PyObject *key, PyObject **a, Py_ssize_t n, Py_ssize_t hint, PyObject *compare) gallop_right(PyObject *key, PyObject **a, Py_ssize_t n, Py_ssize_t hint)
{ {
Py_ssize_t ofs; Py_ssize_t ofs;
Py_ssize_t lastofs; Py_ssize_t lastofs;
@ -1273,9 +1225,6 @@ struct s_slice {
}; };
typedef struct s_MergeState { typedef struct s_MergeState {
/* The user-supplied comparison function. or NULL if none given. */
PyObject *compare;
/* This controls when we get *into* galloping mode. It's initialized /* This controls when we get *into* galloping mode. It's initialized
* to MIN_GALLOP. merge_lo and merge_hi tend to nudge it higher for * to MIN_GALLOP. merge_lo and merge_hi tend to nudge it higher for
* random data, and lower for highly structured data. * random data, and lower for highly structured data.
@ -1306,10 +1255,9 @@ typedef struct s_MergeState {
/* Conceptually a MergeState's constructor. */ /* Conceptually a MergeState's constructor. */
static void static void
merge_init(MergeState *ms, PyObject *compare) merge_init(MergeState *ms)
{ {
assert(ms != NULL); assert(ms != NULL);
ms->compare = compare;
ms->a = ms->temparray; ms->a = ms->temparray;
ms->alloced = MERGESTATE_TEMP_SIZE; ms->alloced = MERGESTATE_TEMP_SIZE;
ms->n = 0; ms->n = 0;
@ -1366,7 +1314,6 @@ merge_lo(MergeState *ms, PyObject **pa, Py_ssize_t na,
PyObject **pb, Py_ssize_t nb) PyObject **pb, Py_ssize_t nb)
{ {
Py_ssize_t k; Py_ssize_t k;
PyObject *compare;
PyObject **dest; PyObject **dest;
int result = -1; /* guilty until proved innocent */ int result = -1; /* guilty until proved innocent */
Py_ssize_t min_gallop; Py_ssize_t min_gallop;
@ -1386,7 +1333,6 @@ merge_lo(MergeState *ms, PyObject **pa, Py_ssize_t na,
goto CopyB; goto CopyB;
min_gallop = ms->min_gallop; min_gallop = ms->min_gallop;
compare = ms->compare;
for (;;) { for (;;) {
Py_ssize_t acount = 0; /* # of times A won in a row */ Py_ssize_t acount = 0; /* # of times A won in a row */
Py_ssize_t bcount = 0; /* # of times B won in a row */ Py_ssize_t bcount = 0; /* # of times B won in a row */
@ -1396,7 +1342,7 @@ merge_lo(MergeState *ms, PyObject **pa, Py_ssize_t na,
*/ */
for (;;) { for (;;) {
assert(na > 1 && nb > 0); assert(na > 1 && nb > 0);
k = ISLT(*pb, *pa, compare); k = ISLT(*pb, *pa);
if (k) { if (k) {
if (k < 0) if (k < 0)
goto Fail; goto Fail;
@ -1431,7 +1377,7 @@ merge_lo(MergeState *ms, PyObject **pa, Py_ssize_t na,
assert(na > 1 && nb > 0); assert(na > 1 && nb > 0);
min_gallop -= min_gallop > 1; min_gallop -= min_gallop > 1;
ms->min_gallop = min_gallop; ms->min_gallop = min_gallop;
k = gallop_right(*pb, pa, na, 0, compare); k = gallop_right(*pb, pa, na, 0);
acount = k; acount = k;
if (k) { if (k) {
if (k < 0) if (k < 0)
@ -1454,7 +1400,7 @@ merge_lo(MergeState *ms, PyObject **pa, Py_ssize_t na,
if (nb == 0) if (nb == 0)
goto Succeed; goto Succeed;
k = gallop_left(*pa, pb, nb, 0, compare); k = gallop_left(*pa, pb, nb, 0);
bcount = k; bcount = k;
if (k) { if (k) {
if (k < 0) if (k < 0)
@ -1498,7 +1444,6 @@ static Py_ssize_t
merge_hi(MergeState *ms, PyObject **pa, Py_ssize_t na, PyObject **pb, Py_ssize_t nb) merge_hi(MergeState *ms, PyObject **pa, Py_ssize_t na, PyObject **pb, Py_ssize_t nb)
{ {
Py_ssize_t k; Py_ssize_t k;
PyObject *compare;
PyObject **dest; PyObject **dest;
int result = -1; /* guilty until proved innocent */ int result = -1; /* guilty until proved innocent */
PyObject **basea; PyObject **basea;
@ -1523,7 +1468,6 @@ merge_hi(MergeState *ms, PyObject **pa, Py_ssize_t na, PyObject **pb, Py_ssize_t
goto CopyA; goto CopyA;
min_gallop = ms->min_gallop; min_gallop = ms->min_gallop;
compare = ms->compare;
for (;;) { for (;;) {
Py_ssize_t acount = 0; /* # of times A won in a row */ Py_ssize_t acount = 0; /* # of times A won in a row */
Py_ssize_t bcount = 0; /* # of times B won in a row */ Py_ssize_t bcount = 0; /* # of times B won in a row */
@ -1533,7 +1477,7 @@ merge_hi(MergeState *ms, PyObject **pa, Py_ssize_t na, PyObject **pb, Py_ssize_t
*/ */
for (;;) { for (;;) {
assert(na > 0 && nb > 1); assert(na > 0 && nb > 1);
k = ISLT(*pb, *pa, compare); k = ISLT(*pb, *pa);
if (k) { if (k) {
if (k < 0) if (k < 0)
goto Fail; goto Fail;
@ -1568,7 +1512,7 @@ merge_hi(MergeState *ms, PyObject **pa, Py_ssize_t na, PyObject **pb, Py_ssize_t
assert(na > 0 && nb > 1); assert(na > 0 && nb > 1);
min_gallop -= min_gallop > 1; min_gallop -= min_gallop > 1;
ms->min_gallop = min_gallop; ms->min_gallop = min_gallop;
k = gallop_right(*pb, basea, na, na-1, compare); k = gallop_right(*pb, basea, na, na-1);
if (k < 0) if (k < 0)
goto Fail; goto Fail;
k = na - k; k = na - k;
@ -1586,7 +1530,7 @@ merge_hi(MergeState *ms, PyObject **pa, Py_ssize_t na, PyObject **pb, Py_ssize_t
if (nb == 1) if (nb == 1)
goto CopyA; goto CopyA;
k = gallop_left(*pa, baseb, nb, nb-1, compare); k = gallop_left(*pa, baseb, nb, nb-1);
if (k < 0) if (k < 0)
goto Fail; goto Fail;
k = nb - k; k = nb - k;
@ -1638,7 +1582,6 @@ merge_at(MergeState *ms, Py_ssize_t i)
PyObject **pa, **pb; PyObject **pa, **pb;
Py_ssize_t na, nb; Py_ssize_t na, nb;
Py_ssize_t k; Py_ssize_t k;
PyObject *compare;
assert(ms != NULL); assert(ms != NULL);
assert(ms->n >= 2); assert(ms->n >= 2);
@ -1664,8 +1607,7 @@ merge_at(MergeState *ms, Py_ssize_t i)
/* Where does b start in a? Elements in a before that can be /* Where does b start in a? Elements in a before that can be
* ignored (already in place). * ignored (already in place).
*/ */
compare = ms->compare; k = gallop_right(*pb, pa, na, 0);
k = gallop_right(*pb, pa, na, 0, compare);
if (k < 0) if (k < 0)
return -1; return -1;
pa += k; pa += k;
@ -1676,7 +1618,7 @@ merge_at(MergeState *ms, Py_ssize_t i)
/* Where does a end in b? Elements in b after that can be /* Where does a end in b? Elements in b after that can be
* ignored (already in place). * ignored (already in place).
*/ */
nb = gallop_left(pa[na-1], pb, nb, nb-1, compare); nb = gallop_left(pa[na-1], pb, nb, nb-1);
if (nb <= 0) if (nb <= 0)
return nb; return nb;
@ -1771,8 +1713,8 @@ merge_compute_minrun(Py_ssize_t n)
pattern. Holds a key which is used for comparisons and the original record pattern. Holds a key which is used for comparisons and the original record
which is returned during the undecorate phase. By exposing only the key which is returned during the undecorate phase. By exposing only the key
during comparisons, the underlying sort stability characteristics are left during comparisons, the underlying sort stability characteristics are left
unchanged. Also, if a custom comparison function is used, it will only see unchanged. Also, the comparison function will only see the key instead of
the key instead of a full record. */ a full record. */
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
@ -1866,104 +1808,6 @@ sortwrapper_getvalue(PyObject *so)
return value; return value;
} }
/* Wrapper for user specified cmp functions in combination with a
specified key function. Makes sure the cmp function is presented
with the actual key instead of the sortwrapper */
typedef struct {
PyObject_HEAD
PyObject *func;
} cmpwrapperobject;
static void
cmpwrapper_dealloc(cmpwrapperobject *co)
{
Py_XDECREF(co->func);
PyObject_Del(co);
}
static PyObject *
cmpwrapper_call(cmpwrapperobject *co, PyObject *args, PyObject *kwds)
{
PyObject *x, *y, *xx, *yy;
if (!PyArg_UnpackTuple(args, "", 2, 2, &x, &y))
return NULL;
if (!PyObject_TypeCheck(x, &PySortWrapper_Type) ||
!PyObject_TypeCheck(y, &PySortWrapper_Type)) {
PyErr_SetString(PyExc_TypeError,
"expected a sortwrapperobject");
return NULL;
}
xx = ((sortwrapperobject *)x)->key;
yy = ((sortwrapperobject *)y)->key;
return PyObject_CallFunctionObjArgs(co->func, xx, yy, NULL);
}
PyDoc_STRVAR(cmpwrapper_doc, "cmp() wrapper for sort with custom keys.");
PyTypeObject PyCmpWrapper_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"cmpwrapper", /* tp_name */
sizeof(cmpwrapperobject), /* tp_basicsize */
0, /* tp_itemsize */
/* methods */
(destructor)cmpwrapper_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)cmpwrapper_call, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
cmpwrapper_doc, /* tp_doc */
};
static PyObject *
build_cmpwrapper(PyObject *cmpfunc)
{
cmpwrapperobject *co;
co = PyObject_New(cmpwrapperobject, &PyCmpWrapper_Type);
if (co == NULL)
return NULL;
Py_INCREF(cmpfunc);
co->func = cmpfunc;
return (PyObject *)co;
}
static int
is_default_cmp(PyObject *cmpfunc)
{
PyCFunctionObject *f;
const char *module;
if (cmpfunc == NULL || cmpfunc == Py_None)
return 1;
if (!PyCFunction_Check(cmpfunc))
return 0;
f = (PyCFunctionObject *)cmpfunc;
if (f->m_self != NULL)
return 0;
if (!PyUnicode_Check(f->m_module))
return 0;
module = PyUnicode_AsString(f->m_module);
if (module == NULL)
return 0;
if (strcmp(module, "builtins") != 0)
return 0;
if (strcmp(f->m_ml->ml_name, "cmp") != 0)
return 0;
return 1;
}
/* An adaptive, stable, natural mergesort. See listsort.txt. /* An adaptive, stable, natural mergesort. See listsort.txt.
* Returns Py_None on success, NULL on error. Even in case of error, the * Returns Py_None on success, NULL on error. Even in case of error, the
* list will be some permutation of its input state (nothing is lost or * list will be some permutation of its input state (nothing is lost or
@ -1979,31 +1823,27 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
Py_ssize_t saved_ob_size, saved_allocated; Py_ssize_t saved_ob_size, saved_allocated;
PyObject **saved_ob_item; PyObject **saved_ob_item;
PyObject **final_ob_item; PyObject **final_ob_item;
PyObject *compare = NULL;
PyObject *result = NULL; /* guilty until proved innocent */ PyObject *result = NULL; /* guilty until proved innocent */
int reverse = 0; int reverse = 0;
PyObject *keyfunc = NULL; PyObject *keyfunc = NULL;
Py_ssize_t i; Py_ssize_t i;
PyObject *key, *value, *kvpair; PyObject *key, *value, *kvpair;
static char *kwlist[] = {"cmp", "key", "reverse", 0}; static char *kwlist[] = {"key", "reverse", 0};
assert(self != NULL); assert(self != NULL);
assert (PyList_Check(self)); assert (PyList_Check(self));
if (args != NULL) { if (args != NULL) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi:sort", if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:sort",
kwlist, &compare, &keyfunc, &reverse)) kwlist, &keyfunc, &reverse))
return NULL;
if (Py_SIZE(args) > 0) {
PyErr_SetString(PyExc_TypeError,
"must use keyword argument for key function");
return NULL; return NULL;
} }
if (is_default_cmp(compare)) }
compare = NULL;
if (keyfunc == Py_None) if (keyfunc == Py_None)
keyfunc = NULL; keyfunc = NULL;
if (compare != NULL && keyfunc != NULL) {
compare = build_cmpwrapper(compare);
if (compare == NULL)
return NULL;
} else
Py_XINCREF(compare);
/* The list is temporarily made empty, so that mutations performed /* The list is temporarily made empty, so that mutations performed
* by comparison functions can't affect the slice of memory we're * by comparison functions can't affect the slice of memory we're
@ -2043,7 +1883,7 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
if (reverse && saved_ob_size > 1) if (reverse && saved_ob_size > 1)
reverse_slice(saved_ob_item, saved_ob_item + saved_ob_size); reverse_slice(saved_ob_item, saved_ob_item + saved_ob_size);
merge_init(&ms, compare); merge_init(&ms);
nremaining = saved_ob_size; nremaining = saved_ob_size;
if (nremaining < 2) if (nremaining < 2)
@ -2060,7 +1900,7 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
Py_ssize_t n; Py_ssize_t n;
/* Identify next run. */ /* Identify next run. */
n = count_run(lo, hi, compare, &descending); n = count_run(lo, hi, &descending);
if (n < 0) if (n < 0)
goto fail; goto fail;
if (descending) if (descending)
@ -2069,7 +1909,7 @@ listsort(PyListObject *self, PyObject *args, PyObject *kwds)
if (n < minrun) { if (n < minrun) {
const Py_ssize_t force = nremaining <= minrun ? const Py_ssize_t force = nremaining <= minrun ?
nremaining : minrun; nremaining : minrun;
if (binarysort(lo, lo + force, lo + n, compare) < 0) if (binarysort(lo, lo + force, lo + n) < 0)
goto fail; goto fail;
n = force; n = force;
} }
@ -2131,7 +1971,6 @@ dsu_fail:
} }
PyMem_FREE(final_ob_item); PyMem_FREE(final_ob_item);
} }
Py_XDECREF(compare);
Py_XINCREF(result); Py_XINCREF(result);
return result; return result;
} }

View file

@ -1494,14 +1494,14 @@ Precision may be negative.");
static PyObject * static PyObject *
builtin_sorted(PyObject *self, PyObject *args, PyObject *kwds) builtin_sorted(PyObject *self, PyObject *args, PyObject *kwds)
{ {
PyObject *newlist, *v, *seq, *compare=NULL, *keyfunc=NULL, *newargs; PyObject *newlist, *v, *seq, *keyfunc=NULL, *newargs;
PyObject *callable; PyObject *callable;
static char *kwlist[] = {"iterable", "cmp", "key", "reverse", 0}; static char *kwlist[] = {"iterable", "key", "reverse", 0};
int reverse; int reverse;
/* args 1-4 should match listsort in Objects/listobject.c */ /* args 1-3 should match listsort in Objects/listobject.c */
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OOi:sorted", if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Oi:sorted",
kwlist, &seq, &compare, &keyfunc, &reverse)) kwlist, &seq, &keyfunc, &reverse))
return NULL; return NULL;
newlist = PySequence_List(seq); newlist = PySequence_List(seq);
@ -1533,7 +1533,7 @@ builtin_sorted(PyObject *self, PyObject *args, PyObject *kwds)
} }
PyDoc_STRVAR(sorted_doc, PyDoc_STRVAR(sorted_doc,
"sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list"); "sorted(iterable, key=None, reverse=False) --> new sorted list");
static PyObject * static PyObject *
builtin_vars(PyObject *self, PyObject *args) builtin_vars(PyObject *self, PyObject *args)