Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c50846a

Browse files
committed
Forward port total_ordering() and cmp_to_key().
1 parent 5daab45 commit c50846a

10 files changed

Lines changed: 187 additions & 26 deletions

File tree

Doc/library/functions.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,9 +1014,8 @@ are always available. They are listed here in alphabetical order.
10141014
*reverse* is a boolean value. If set to ``True``, then the list elements are
10151015
sorted as if each comparison were reversed.
10161016

1017-
To convert an old-style *cmp* function to a *key* function, see the
1018-
`CmpToKey recipe in the ASPN cookbook
1019-
<http://code.activestate.com/recipes/576653/>`_\.
1017+
Use :func:`functools.cmp_to_key` to convert an
1018+
old-style *cmp* function to a *key* function.
10201019

10211020
For sorting examples and a brief sorting tutorial, see `Sorting HowTo
10221021
<http://wiki.python.org/moin/HowTo/Sorting/>`_\.

Doc/library/functools.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,51 @@ function for the purposes of this module.
1515

1616
The :mod:`functools` module defines the following functions:
1717

18+
.. function:: cmp_to_key(func)
19+
20+
Transform an old-style comparison function to a key-function. Used with
21+
tools that accept key functions (such as :func:`sorted`, :func:`min`,
22+
:func:`max`, :func:`heapq.nlargest`, :func:`heapq.nsmallest`,
23+
:func:`itertools.groupby`).
24+
This function is primarily used as a transition tool for programs
25+
being converted from Py2.x which supported the use of comparison
26+
functions.
27+
28+
A compare function is any callable that accept two arguments, compares
29+
them, and returns a negative number for less-than, zero for equality,
30+
or a positive number for greater-than. A key function is a callable
31+
that accepts one argument and returns another value that indicates
32+
the position in the desired collation sequence.
33+
34+
Example::
35+
36+
sorted(iterable, key=cmp_to_key(locale.strcoll)) # locale-aware sort order
37+
38+
.. versionadded:: 3.2
39+
40+
.. function:: total_ordering(cls)
41+
42+
Given a class defining one or more rich comparison ordering methods, this
43+
class decorator supplies the rest. This simplies the effort involved
44+
in specifying all of the possible rich comparison operations:
45+
46+
The class must define one of :meth:`__lt__`, :meth:`__le__`,
47+
:meth:`__gt__`, or :meth:`__ge__`.
48+
In addition, the class should supply an :meth:`__eq__` method.
49+
50+
For example::
51+
52+
@total_ordering
53+
class Student:
54+
def __eq__(self, other):
55+
return ((self.lastname.lower(), self.firstname.lower()) ==
56+
(other.lastname.lower(), other.firstname.lower()))
57+
def __lt__(self, other):
58+
return ((self.lastname.lower(), self.firstname.lower()) <
59+
(other.lastname.lower(), other.firstname.lower()))
60+
61+
.. versionadded:: 3.2
62+
1863
.. function:: partial(func, *args, **keywords)
1964

2065
Return a new :class:`partial` object which when called will behave like *func*

Doc/library/stdtypes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1567,6 +1567,9 @@ Notes:
15671567

15681568
*key* specifies a function of one argument that is used to extract a comparison
15691569
key from each list element: ``key=str.lower``. The default value is ``None``.
1570+
Use :func:`functools.cmp_to_key` to convert an
1571+
old-style *cmp* function to a *key* function.
1572+
15701573

15711574
*reverse* is a boolean value. If set to ``True``, then the list elements are
15721575
sorted as if each comparison were reversed.

Doc/reference/datamodel.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,8 +1209,7 @@ Basic customization
12091209
Arguments to rich comparison methods are never coerced.
12101210

12111211
To automatically generate ordering operations from a single root operation,
1212-
see the `Total Ordering recipe in the ASPN cookbook
1213-
<http://code.activestate.com/recipes/576529/>`_\.
1212+
see :func:`functools.total_ordering`.
12141213

12151214
.. method:: object.__hash__(self)
12161215

Lib/functools.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,50 @@ def wraps(wrapped,
4949
"""
5050
return partial(update_wrapper, wrapped=wrapped,
5151
assigned=assigned, updated=updated)
52+
53+
def total_ordering(cls):
54+
'Class decorator that fills-in missing ordering methods'
55+
convert = {
56+
'__lt__': [('__gt__', lambda self, other: other < self),
57+
('__le__', lambda self, other: not other < self),
58+
('__ge__', lambda self, other: not self < other)],
59+
'__le__': [('__ge__', lambda self, other: other <= self),
60+
('__lt__', lambda self, other: not other <= self),
61+
('__gt__', lambda self, other: not self <= other)],
62+
'__gt__': [('__lt__', lambda self, other: other > self),
63+
('__ge__', lambda self, other: not other > self),
64+
('__le__', lambda self, other: not self > other)],
65+
'__ge__': [('__le__', lambda self, other: other >= self),
66+
('__gt__', lambda self, other: not other >= self),
67+
('__lt__', lambda self, other: not self >= other)]
68+
}
69+
roots = set(dir(cls)) & set(convert)
70+
assert roots, 'must define at least one ordering operation: < > <= >='
71+
root = max(roots) # prefer __lt __ to __le__ to __gt__ to __ge__
72+
for opname, opfunc in convert[root]:
73+
if opname not in roots:
74+
opfunc.__name__ = opname
75+
opfunc.__doc__ = getattr(int, opname).__doc__
76+
setattr(cls, opname, opfunc)
77+
return cls
78+
79+
def cmp_to_key(mycmp):
80+
'Convert a cmp= function into a key= function'
81+
class K(object):
82+
def __init__(self, obj, *args):
83+
self.obj = obj
84+
def __lt__(self, other):
85+
return mycmp(self.obj, other.obj) < 0
86+
def __gt__(self, other):
87+
return mycmp(self.obj, other.obj) > 0
88+
def __eq__(self, other):
89+
return mycmp(self.obj, other.obj) == 0
90+
def __le__(self, other):
91+
return mycmp(self.obj, other.obj) <= 0
92+
def __ge__(self, other):
93+
return mycmp(self.obj, other.obj) >= 0
94+
def __ne__(self, other):
95+
return mycmp(self.obj, other.obj) != 0
96+
def __hash__(self):
97+
raise TypeError('hash not implemented')
98+
return K

Lib/pstats.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import time
3838
import marshal
3939
import re
40+
from functools import cmp_to_key
4041

4142
__all__ = ["Stats"]
4243

@@ -226,7 +227,7 @@ def sort_stats(self, *field):
226227
stats_list.append((cc, nc, tt, ct) + func +
227228
(func_std_string(func), func))
228229

229-
stats_list.sort(key=CmpToKey(TupleComp(sort_tuple).compare))
230+
stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
230231

231232
self.fcn_list = fcn_list = []
232233
for tuple in stats_list:
@@ -458,15 +459,6 @@ def compare (self, left, right):
458459
return direction
459460
return 0
460461

461-
def CmpToKey(mycmp):
462-
'Convert a cmp= function into a key= function'
463-
class K(object):
464-
def __init__(self, obj):
465-
self.obj = obj
466-
def __lt__(self, other):
467-
return mycmp(self.obj, other.obj) == -1
468-
return K
469-
470462

471463
#**************************************************************************
472464
# func_name is a triple (file:string, line:int, name:string)

Lib/test/test_functools.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,89 @@ def __getitem__(self, i):
364364
d = {"one": 1, "two": 2, "three": 3}
365365
self.assertEqual(self.func(add, d), "".join(d.keys()))
366366

367-
367+
class TestCmpToKey(unittest.TestCase):
368+
def test_cmp_to_key(self):
369+
def mycmp(x, y):
370+
return y - x
371+
self.assertEqual(sorted(range(5), key=functools.cmp_to_key(mycmp)),
372+
[4, 3, 2, 1, 0])
373+
374+
def test_hash(self):
375+
def mycmp(x, y):
376+
return y - x
377+
key = functools.cmp_to_key(mycmp)
378+
k = key(10)
379+
self.assertRaises(TypeError, hash(k))
380+
381+
class TestTotalOrdering(unittest.TestCase):
382+
383+
def test_total_ordering_lt(self):
384+
@functools.total_ordering
385+
class A:
386+
def __init__(self, value):
387+
self.value = value
388+
def __lt__(self, other):
389+
return self.value < other.value
390+
self.assert_(A(1) < A(2))
391+
self.assert_(A(2) > A(1))
392+
self.assert_(A(1) <= A(2))
393+
self.assert_(A(2) >= A(1))
394+
self.assert_(A(2) <= A(2))
395+
self.assert_(A(2) >= A(2))
396+
397+
def test_total_ordering_le(self):
398+
@functools.total_ordering
399+
class A:
400+
def __init__(self, value):
401+
self.value = value
402+
def __le__(self, other):
403+
return self.value <= other.value
404+
self.assert_(A(1) < A(2))
405+
self.assert_(A(2) > A(1))
406+
self.assert_(A(1) <= A(2))
407+
self.assert_(A(2) >= A(1))
408+
self.assert_(A(2) <= A(2))
409+
self.assert_(A(2) >= A(2))
410+
411+
def test_total_ordering_gt(self):
412+
@functools.total_ordering
413+
class A:
414+
def __init__(self, value):
415+
self.value = value
416+
def __gt__(self, other):
417+
return self.value > other.value
418+
self.assert_(A(1) < A(2))
419+
self.assert_(A(2) > A(1))
420+
self.assert_(A(1) <= A(2))
421+
self.assert_(A(2) >= A(1))
422+
self.assert_(A(2) <= A(2))
423+
self.assert_(A(2) >= A(2))
424+
425+
def test_total_ordering_ge(self):
426+
@functools.total_ordering
427+
class A:
428+
def __init__(self, value):
429+
self.value = value
430+
def __ge__(self, other):
431+
return self.value >= other.value
432+
self.assert_(A(1) < A(2))
433+
self.assert_(A(2) > A(1))
434+
self.assert_(A(1) <= A(2))
435+
self.assert_(A(2) >= A(1))
436+
self.assert_(A(2) <= A(2))
437+
self.assert_(A(2) >= A(2))
438+
439+
def test_total_ordering_no_overwrite(self):
440+
# new methods should not overwrite existing
441+
@functools.total_ordering
442+
class A(int):
443+
raise Exception()
444+
self.assert_(A(1) < A(2))
445+
self.assert_(A(2) > A(1))
446+
self.assert_(A(1) <= A(2))
447+
self.assert_(A(2) >= A(1))
448+
self.assert_(A(2) <= A(2))
449+
self.assert_(A(2) >= A(2))
368450

369451

370452
def test_main(verbose=None):

Lib/unittest/loader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import sys
66
import traceback
77
import types
8+
import functools
89

910
from fnmatch import fnmatch
1011

@@ -141,7 +142,7 @@ def isTestMethod(attrname, testCaseClass=testCaseClass,
141142
testFnNames = testFnNames = list(filter(isTestMethod,
142143
dir(testCaseClass)))
143144
if self.sortTestMethodsUsing:
144-
testFnNames.sort(key=util.CmpToKey(self.sortTestMethodsUsing))
145+
testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
145146
return testFnNames
146147

147148
def discover(self, start_dir, pattern='test*.py', top_level_dir=None):

Lib/unittest/util.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,6 @@ def unorderable_list_difference(expected, actual):
7070
# anything left in actual is unexpected
7171
return missing, actual
7272

73-
def CmpToKey(mycmp):
74-
'Convert a cmp= function into a key= function'
75-
class K(object):
76-
def __init__(self, obj, *args):
77-
self.obj = obj
78-
def __lt__(self, other):
79-
return mycmp(self.obj, other.obj) == -1
80-
return K
81-
8273
def three_way_cmp(x, y):
8374
"""Return -1 if x < y, 0 if x == y and 1 if x > y"""
8475
return (x > y) - (x < y)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ C-API
303303
Library
304304
-------
305305

306+
- Add functools.total_ordering() and functools.cmp_to_key().
307+
306308
- Issue #8257: The Decimal construct now accepts a float instance
307309
directly, converting that float to a Decimal of equal value:
308310

0 commit comments

Comments
 (0)