Зачем этот пост? Стандартный параметр в стандартной функции, о котором написано сотни статей и примеров. Только понимание этого инструмента ко мне пришло после прочтения небольшого лирического отступления в теме о последовательностях в книге Р. Лучано «Python. К вершинам мастерства». Если коротко, мысль следующая: использование функции в качестве параметра key эффективно, потому что вызывается только один раз для каждого элемента последовательности. В общем-то можно на этом и закончить. Однако, я вспомнил про описание лямбда функций в книге Д. Бейдера «Чистый Python», в которой указано, что наиболее удачный и часто используемый вариант применения лямбд — это сортировка по альтернативному ключу. Хороший повод разобрать применение и ключей при сортировки и лямбд. Если нет понимания, как работает инструмент, что остается? «Копипастить» со stackoverflow без возможности гибко использовать мощь стандартного инструмента языка. Если Вам, как и мне когда-то, сложно понять, что написано, давайте нарисуем!
Предположим у нас есть список с именами изображений:
>>> list_img = ['img_0', 'img_01', 'img_2', 'img_11', 'img_111', 'img_3']
Так как они все одного типа (str), возможность отсортировать сохраняется. Применим функцию sorted:
>>> list_img = sorted(list_img)
>>> list_img
['img_0', 'img_01', 'img_11', 'img_111', 'img_2', 'img_3']
Возможно, Вы ожидали такого вывода, если нет — почитайте о сортировке строк. Если вы хотели бы, что бы img_2 был перед img_11, а img_111 был в самом конце, то строки сортируются по первому различному символу, например список:
>>> list_img = ['img_0', 'amg_01', 'img_2', 'img_11', 'img_111', 'img_3']
будет отсортирован следующим образом:
>>> list_img = sorted(list_img)
>>> list_img
['amg_01', 'img_0', 'img_11', 'img_111', 'img_2', 'img_3']
Допустим. А как в таком случае получить ожидаемый результат сортировки по номеру в названии изображения? Надо просто применить ключ!
Вернемся к списку:
>>> list_img = ['img_0', 'img_01', 'img_2', 'img_11', 'img_111', 'img_3']
>>> list_img = sorted(list_img, key=number_in_image_title)
>>> list_img
['img_0', 'img_01', 'img_2', 'img_3', 'img_11', 'img_111']
Выглядит логично и ожидаемо, например, с точки зрения порядка обработки этих изображений в будущем. Что в данном случае является ключом number_in_image_title (номер в названии изображения)? Это функция, которая принимает один аргумент — название изображения, обрабатывает его и возвращает номер, который содержится в названии. Например, такая функция:
>>> def number_in_image_title(image_title):
>>> digit_str = ''
>>> for character in image_title:
>>> if character.isdigit():
>>> digit_str += character
>>> return int(digit_str)
Воспользуемся визуализацией процессов на сайте pythontutor.
Когда мы дошли до строки с сортировкой списка:

Мы имеем два объекта в глобальной области: функцию number_in_image_title и список list_img.

Далее, функция sorted, вызывает объект, который указан в качестве аргумента параметра key - number_in_image_title, а в качестве аргумента для number_in_image_title использует первый элемент итерируемого списка (в данном случае „img_0“), переданного для сортировки (list_img).


Проходя дальше по циклу (3-5 строчка кода) мы проверяем, является ли каждый символ цифрой, и если является конкатенируем с пустой строкой digit_str.





Функция number_in_image_title возвращает нам цифру 0. Следующим шагом функция sorted вызовет number_in_image_title с аргументом «img_01», digit_str будет «01», а вернет функция 1:

И так далее, пока все элементы списка не будут обработаны функцией, указанной как аргумент параметра key.
Что получается? Без ключа мы имеем список:
['img_0', 'img_01', 'img_2', 'img_11', 'img_111', 'img_3']
который сортируется, согласно правилам сортировки строк. С ключом мы имеем тот же список, но сортируем не его, а фактически мы сортируем возвращаемые значения от функции key:
[0, 1, 2, 11, 111, 3]
Новый список связан с оригинальным индексами позиций элементов.
['img_0', 'img_01', 'img_2', 'img_11', 'img_111', 'img_3' ]
| | | | | |
[ 0, 1, 2, 11, 111, 3 ]
Теперь, если мы должны поместить цифру 3 в позицию 3-его элемента при сортировке нового списка, то «img_3», соответственно, будет помещен в позицию 3-его элемента в конечном отсортированном списке. Аналогично и с остальными элементами оригинального и нового списка.
В итоге следующий код:
def number_in_image_title(image_title):
digit_str = ''
for character in image_title:
if character.isdigit():
digit_str += character
return int(digit_str)
list_img = ['img_0', 'img_01', 'img_2', 'img_11', 'img_111', 'img_3']
list_img = sorted(list_img, key=number_in_image_title)
print(list_img)
даст нам ожидаемый, с точки зрения сортировки по номеру изображения результат:
['img_0', 'img_01', 'img_2', 'img_3', 'img_11', 'img_111']
Таким образом, мы можем отсортировать итерируемый объект, с использованием любой логики сортировки, используя ключ, которым является функция, принимающая один аргумент и этот аргумент — элемент итерируемого списка.
Хорошо, но где же тут лямбда функция? В Python лямбда функции — не именованные однострочные функции. Есть ли у Вас опыт решения многоэтапных задач в одну строчку? Если Вы не понимаете о чем речь, посетите эти страницы: Powerful Python One-Liners, One-lined Python. А теперь посмотрите на код функции number_in_image_title. Понимаете ли Вы как можно решить задачу определения числа/цифры в строке (например, «img_0») в одну строчку? Если да, тогда у Вас нет с этим проблем, просто оберните эту логику в лямбда. Если нет, то вот возможное решение:
>>> int(''.join([character for character in „img_0“ if character.isdigit()]))
out: 0
Функция сортировки с использованием лябда будет выглядеть следующим образом:
list_img = sorted(list_img, key=lambda image_title: int(''.join([character for character in image_title if character.isdigit()])))
Вместо 6 строк кода и 1 функции мы использовали 0 строк кода, 0 функций, если не считать лямбда, которую мы объявили и сразу применили в качестве аргумента для параметра key! Это мощно! Но возможно трудно читаемо, особенно для новичков.
Можете быть уверены, что и результат и процесс ничем не отличается от варианта с функцией number_in_image_title. А впрочем, не стоит верить на слово, вот код для этого варианта (плюс еще и с визуализацией!), посмотрите и проверьте сами.
Логика та же, только вместо названия функции number_in_image_title у нас lambda, с тем же параметром image_title, принимающая в качестве аргумента элемент итерируемого списка list_img. join — метод строки, объединяет все цифры из конкретного элемента list_img, который передан в lambda как image_title. Цифры мы получаем как результат работы генератора списков (List Comprehension). Ну вот и всё! Практикуйтесь, читайте чужой код, не бойтесь нового и пытайтесь понять смысл выражений.
Читай: