Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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