Skip to content
11 changes: 10 additions & 1 deletion Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1840,7 +1840,7 @@ are always available. They are listed here in alphabetical order.
Slice objects are now :term:`hashable` (provided :attr:`~slice.start`,
:attr:`~slice.stop`, and :attr:`~slice.step` are hashable).

.. function:: sorted(iterable, /, *, key=None, reverse=False)
.. function:: sorted(iterable, /, *, key=None, keylist=None, reverse=False)

Return a new sorted list from the items in *iterable*.

Expand All @@ -1850,6 +1850,10 @@ are always available. They are listed here in alphabetical order.
key from each element in *iterable* (for example, ``key=str.lower``). The
default value is ``None`` (compare the elements directly).

Alternative to key function is supplying a :class:`list` object
to *keylist* argument, which will determine the sort order.
Provided :class:`list` object will be modified in place.

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

Expand All @@ -1872,6 +1876,11 @@ are always available. They are listed here in alphabetical order.

For sorting examples and a brief sorting tutorial, see :ref:`sortinghowto`.

.. versionchanged:: 3.15

Added *keylist* argument.


.. decorator:: staticmethod

Transform a method into a static method.
Expand Down
11 changes: 10 additions & 1 deletion Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1394,7 +1394,7 @@ application).
:ref:`mutable <typesseq-mutable>` sequence operations. Lists also provide the
following additional method:

.. method:: list.sort(*, key=None, reverse=False)
.. method:: list.sort(*, key=None, keylist=None, reverse=False)

This method sorts the list in place, using only ``<`` comparisons
between items. Exceptions are not suppressed - if any comparison operations
Expand All @@ -1414,6 +1414,10 @@ application).
The :func:`functools.cmp_to_key` utility is available to convert a 2.x
style *cmp* function to a *key* function.

Alternative to key function is supplying a :class:`list` object
to *keylist* argument, which will determine the sort order.
Provided :class:`list` object will be modified in place.

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

Expand All @@ -1436,6 +1440,11 @@ application).
list appear empty for the duration, and raises :exc:`ValueError` if it can
detect that the list has been mutated during a sort.

The same applies to *keylist* argument.

.. versionchanged:: 3.15

Added *keylist* argument.

.. _typesseq-tuple:

Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,13 @@ argparse
default to ``True``. This enables suggestions for mistyped arguments by default.
(Contributed by Jakob Schluse in :gh:`140450`.)

builtins
--------
* :func:`sorted` and :meth:`list.sort` now accept *keylist* argument,
which takes :class:`list` object by the keys of which the sorting takes place.
*keylist* argument is sorted in-place (i.e. is modified).
(Contributed by Dominykas Grigonis in :gh:`142105`.)

calendar
--------

Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(keepends)
STRUCT_FOR_ID(key)
STRUCT_FOR_ID(keyfile)
STRUCT_FOR_ID(keylist)
STRUCT_FOR_ID(keys)
STRUCT_FOR_ID(kind)
STRUCT_FOR_ID(kw)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

65 changes: 65 additions & 0 deletions Lib/test/test_sort.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,5 +407,70 @@ def test_none_in_tuples(self):

#==============================================================================

class TestKeylist(unittest.TestCase):
def test_exclusivity_with_key(self):
msg = 'Only one of key and keylist can be provided.'
with self.assertRaisesRegex(ValueError, msg):
[].sort(key=1, keylist=1)

def test_argtype(self):
for arg in [1, (), iter(())]:
msg = f"'{type(arg).__name__}' object is not a list"
with self.assertRaisesRegex(TypeError, msg):
[].sort(keylist=arg)

def test_unequal_sizes(self):
msg = 'Lengths of input list and keylist differ.'
for arg in [[1, 2], [1, 2, 3, 4]]:
with self.assertRaisesRegex(ValueError, msg):
[1, 2, 3].sort(keylist=arg)

def test_empty(self):
data = []
keylist = []
data.sort(keylist=keylist)
self.assertEqual(data, [])
self.assertEqual(keylist, [])

def test_keylist_vs_key(self):
for reverse in [False, True]:
data = list(range(10))
# NOTE: BORLAND32-RNG-LIKE
keyfunc = lambda x: ((22695477 * x + 1) % 2**32) % 10
keylist = list(map(keyfunc, data))
res_keyfunc = sorted(data, key=keyfunc, reverse=reverse)
res_keylist = sorted(data, keylist=keylist, reverse=reverse)
self.assertEqual(res_keyfunc, res_keylist)

def test_mutability_plus(self):
for reverse in [False, True]:
for size in [10, 100, 1000]:
data = list(range(size))
# NOTE: BORLAND32-RNG-LIKE
keyfunc = lambda x: ((22695477 * x + 1) % 2**32) % size
keylist = list(map(keyfunc, data))
orig_keylist = list(keylist)

expected_keylist = sorted(keylist, reverse=reverse)
result = sorted(data, keylist=keylist, reverse=reverse)
self.assertEqual(keylist, expected_keylist)

# And for completeness check the result
rge = range(len(keylist))
idxs = sorted(rge, key=orig_keylist.__getitem__, reverse=reverse)
expected_result = [data[i] for i in idxs]
self.assertEqual(result, expected_result)

def test_mid_failure(self):
values = list(range(5))
keylist = [2, 1, 3, 0, None]
with self.assertRaises(TypeError):
values.sort(keylist=keylist)

expected_values = sorted(range(4), keylist=[2, 1, 3, 0])
self.assertEqual(values, expected_values + [4])
self.assertEqual(keylist, [0, 1, 2, 3, None])


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`sorted` and :meth:`list.sort` now accept *keylist* argument, which takes :class:`list` object by the keys of which the sorting takes place. *keylist* argument is sorted in-place (i.e. is modified).
33 changes: 23 additions & 10 deletions Objects/clinic/listobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading