|
|||||||
Лямбда-функции в языке Python. Использование map/filter/reduce. Простое объяснение
Время создания: 19.04.2023 08:52
Автор: Xintrea
Текстовые метки: python, map, filter, reduce, лямбла, lambda, функция, просто, функциональное, программирование
Раздел: Компьютер - Программирование - Язык Python
Запись: xintrea/mytetra_syncro/master/base/16818835543q2t2gx7zs/text.html на raw.github.com
|
|||||||
|
|||||||
Триада функций map/filter/reduce пришла в язык Python из функционального программирования. Самый известный функциональный язык программирования называется Lisp, что расшифровывается как List Processing language — т. е. «язык обработки списков». А это значит, что данные механизмы предназначены для массовой обработки списков. И если быть еще точнее — для обработки элементов списка. Но прежде чем начать разбираться с функциями обработки списков, надо вникнуть в еще одно базовое понятие функционального программирования — в лямбда-функции. Что такое лямбда-функция? Лямбда-функция - это такая же функция как и любая другая, только безымянная. А так как у нее нет имени, то к данной функции можно обратиться только в том месте, где она и написана. Вот и все. Как следствие, лямда-функция - это "одноразовая" функция. Из-за того что она безымянная, ее можно использовать, буквально, только в том месте, где она написана. То есть, невозможно вызвать лямбда-функцию из нескольких мест в коде. Обычно, классические функции часто пишут для того, чтобы избежать дублирования кода либо для лучшего структурирования программы. В любом случае, у классических функций есть место описания функции и, как минимум, одно место вызова функции. Лямбда-функция, в свою очередь, используется только в одном месте, а именно там, где она написана, и все. Зачем такие функции нужны? Обычно, лямда-функции - это маленькие, короткие функции с одним или несколькими действиями внутри. Нужны лямда-функции для программистов-функциональщиков, которым неохота писать по всем синтаксическим правилам отдельные классические функции. Ведь классические функции должны быть оформлены где-то в коде, функции нужно придумать имя, а содержимое кода функции часто не видно на экране когда пишется основной код, и все это так неудобно... Так вот, для замены этих рутинных действий был создан синтаксис, который дает возможность написать код функции прямо в том месте, где она должна быть использована. Пример обычной функции: def standard_function(x, y): return x + y Пример лямбда-функции, которая выполняет те же действия что и standard_function(): lambda x, y: x + y Следует обратить внимание, что в общем случае лямбда-функции могут иметь произвольное количество аргументов, как и обычные функции. Чтобы продемонстрировать, как лямда-функции используются прямо в том месте, где они написаны, ниже дан пример такого использования. Этот пример достаточно бессмысленный, но дает представление как работает лямбда-функция: a = (lambda x, y: x+y)(5, 7) print(a) Результат: 12 Синтаксис описания лямбда-функции, как уже стало понятно из примеров, следующий: lambda <аргументы функции через запятую> : тело функции Передача параметров в лямбда-функцию сделана конструкцией (5, 7). То есть, если в классическом вызове обычной функции пишется имя функции и параметры в круглых скобках, то при вызове лямбда-функции пишется сама лямбда, а после нее - параметры в круглых скобках. Существует ограничение. В языке Python тело функции (если не применять специальных трюков) может состоять только из одного выражения. Да, вот такое ограничение. Сделать лямбда-функцию даже из двух последовательно идущих команд в Python невозможно. Такое ограничение введено, в частности, и из-за общего Python-синтаксиса, а так же из-за того, что в питоновских лямбдах не предусмотрена возможность существования стейтов (statements). То есть внутри питоновской лямбда-функции невозможно сохранить значение какой-либо переменной, чтобы использовать это значение в последующих командах внутри этой же функции. В общем, нет ни стейтов, ни возможности cделать несколько команд. Поэтому в питоновской лямбде может быть только одно выражение. Значит ли это, что лямбда-функции в Python так ограничены, и из-за этих ограничений невозможно делать некоторые алгоритмы? Нет, потому что вместо лямбда-функции всегда можно написать и использовать обычную именованную функцию. Да, это будет более многословно, но по факту ничего принципиально не поменяется. В синтаксисе написания лямбда-функции так же видна еще одна особенность: в лямбда-функции нет операции return. То есть, нет явной операции возврата вычисленного выражения. В качестве возвращаемого значения выступает значение выражения, записанного в лямбда-функции. Это сделано потому, что тело лямбда-функции состоит только из одного выражения. Примечание для особо дотошных. Ранее было сказано, что к лямбда-функции можно обратиться только в том месте, где она написана, и что невозможно вызывать лямбда-функцию из нескольких мест в коде. Да, это так, но совершенно не запрещено, например, такое использование: ourFunction = lambda x, y: x + y ... a=ourFunction(10, 90) b=ourFunction(15, 1000) Здесь, по сути, через лямбда-функцию создается именованная функция. А вызов именованной функции можно делать из разных мест в коде. Следует обратить внимание, что в этом примере все так же нет определения обычной функции через стандартную конструкцию def. Функция определена прямо в коде, и присвоена переменной. Такое inline-определение возможно делать именно благодаря механизму лямбда-функций. Функции обработки списков (контейнеров) Если в программе есть списки или другие контейнеры, то часто возникает необходимость как-то их обработать: по какому-то алгоритму изменить каждый элемент, или пробежаться по всем элементам и собрать с них какие-то обобщающие данные (метрики). Для этих целей хорошо подходит триада функций map(), filter(), reduce(). В качестве входящих обрабатываемых последовательностей обычно используются списки или кортежи. Функция Map Встроенная в Python функция map() – это функция, которая позволяет применить некую пользовательскую функцию для всех элементов в последовательности. В функциональном программировании функция map называется отображением. Общий формат функции map() следующий: result = map(пользовательская функция, последовательность) или result = map(пользовательская функция, последовательности через запятую) В качестве пользовательской функции следует указать либо имя обычной функции, либо прямо на этом месте написать лямбда-функцию. Если обрабатывается одна последовательность, то у пользовательской функции должен быть один параметр. В этот параметр, в момент обработки, будет засовываться значение очередного элемента последовательности. Если обрабатывается несколько последовательностей, тогда у пользовательской функции должно быть столько параметров, сколько задано входящих последовательностей. В первый параметр пользовательской функции будет вставляться элемент первой последовательности, во второй параметр - элемент второй последовательности и т.д. Итерирование элементов на каждом шаге обработки идет одновременно по всем последовательностям. То есть, на первом шаге в пользовательскую функцию попадут первые элементы всех последовательностей, на втором шаге - вторые элементы последовательностей и т. д. При обработке нескольких последовательностей нужно следить, чтобы их длина была одинаковой. Если длина последовательностей разная, то произойдет обработка только того количества элементов, доступных в самой кроткой последовательности, так как для последующих вызовов пользовательской функции будет не хватать значений параметров. Важно знать, что функция map() возвращает не итоговую последовательность, а объект типа map. Чтобы из этого объекта "достать" итоговый список, нужно к нему применить функцию list(). Таким образом реализуется концепция ленивых вычислений: результат работы вычисляется только тогда, когда он действительно нужен. То есть, итоговая последовательность вычисляется не в момент вызова функции map(), а в момент запроса итогового списка. Пример обработки одной последовательности: seq = (1, 2, 3, 4, 5, 6, 7, 8, 9) result = map(lambda x : x * x + 10, seq) resultList = list(result) print(resultList) Результат: [11, 14, 19, 26, 35, 46, 59, 74, 91] Часто map() и list() объединяют в одну конструкцию: result = list(map(lambda x : x * x + 10, seq)) print(result) Пример обработки двух последовательностей: seq = (1, 2, 3, 4, 5, 6, 7, 8, 9) seq2 = (5, 6, 7, 8, 9, 0, 3, 2, 1) result = list(map(lambda x, y: x+y, seq, seq2)) print(result) Результат: [6, 8, 10, 12, 14, 6, 10, 10, 10] А вот что будет если одна последовательность окажется короче другой: seq = (1, 2, 3, 4, 5, 6, 7, 8, 9) seq2 = (5, 6, 7, 8, 9) result = list(map(lambda x, y: x+y, seq, seq2)) print(result) Результат: [6, 8, 10, 12, 14] Функция filter() Функция filter() берет элементы исходной последовательности, и оставляет в ней только те элементы, которые удовлетворяют функции фильтрации. Функция фильтрации обязательно должна возвращать булево значение. В общем виде функция filter() записывается следующим образом: result = filter(фильтрующая функция, последовательность) В отличие от map(), функция filter() может обрабатывать только одну последовательность. И поэтому у фильтрующей функции может быть только один аргумент. Вот пример кода, который выбирает из последовательности только четные числа: seq = (1, 2, 3, 4, 5, 6, 7, 8, 9) filtered = list( filter(lambda x: x % 2 == 0, seq) ) print( filtered ) Результат: [2, 4, 6, 8] Функция reduce() В переводе с английского, слово "reduce" означает "свертка" или "сведение". Имеется в виду, что последовательность элементов сводится по некоторому алгоритму к какому-то одному значению. Для этого и была создана функция reduce(). Функция reduce() обрабатывает последовательность с помощью заданной пользователем функции, возвращая в результате одно-единственное, "накопленное" значение. Результат работы функции reduce() не новая последовательность, а какое-то одно значение. Такое значение ещё можно назвать метрикой. Какие метрики можно снимать с последовательностей? Например, если последовательность состоит из чисел, можно получить такие метрики:
Можно придумать и другие метрики, но тут главное понять принцип, каким образом метрика высчитывается. При высчитывании метрики надо иметь переменную, в которой будет находиться значение метрики. Эта переменная должна каждый раз передаваться в пользовательскую функцию, т. е. каждый раз при обработке очередного элемента последовательности. Принято, что метрика передается в первом аргументе пользовательской функции. Вторым аргументом пользовательской функции передается значение очередного обрабатываемого элемента. Пользовательская функция что-то делает с полученными ею текущим значением метрики и значением текущего элемента (например, складывает их), и возвращает вычисленное значение. На следующем шаге обработки списка, это возвращенное значение снова засовывается в пользовательскую функцию, в виде первого аргумента. И так происходит до тех пор, пока список не будет обработан. Таким образом, на самом деле, переменной с текущим значением метрики (ее еще называют аккумулирующим значением, или аккумулятором, или накопительным значением, накопителем), в явном виде в коде нет. Значение метрики как бы постоянно передается в первый аргумент пользовательской функции, возвращается в качестве результата работы пользовательской функции, и снова передается в первый аргумент, и таким образом циркулирует до тех пор, пока не закончится последовательность. Последнее значение, полученное из пользовательской функции, называется сверткой. А сама пользовательская функция - функцией свертки. Функция reduce(), по сути, возвращает свертку, т. е. значение, полученное из пользовательской функции на последней итерации обработки списка. Примечание: Как видно из всего вышесказанного, пользовательская функция, используемая в reduce(), должна иметь только два аргумента. Общий формат функции reduce() следующий: reduce(пользовательская функция, последовательность, начальное значение метрики) Ниже дан пример нахождения максимального значения среди заданных натуральных чисел, т. е. это некоторый аналог функции max(): numberList = [1, 24, 17, 14, 9, 32, 2] listMax = reduce(lambda a, b : a if (a > b) else b, numberList, 0) print(listMax) Чтобы показать, что последовательность может состоять не только из чисел, в следующем примере дана последовательность строк, в которых написано длинное стихотворение. Задача: подсчитать количество слов во всем стихотворении. sentences = ["Варкалось.", "Хливкие шорьки пырялись по наве,", "и хрюкотали зелюки, как мюмзики в мове."] wordCountFunction = lambda count, text : count + len(text.split()) numberOfWords = reduce(wordCountFunction, sentences, 0) print(numberOfWords) Здесь в пользовательской функции первый аргумент - count - является накопителем, а второй аргумент - text - является текстом очередной обрабатываемой строки. Начальное значение count задается в третьем аргументе функции reduce(). * * * На этом беглый обзор работы функций map/filter/reduce можно закончить. На самом деле, концепция обработки последовательностей через функции довольно проста, и используется во многих языках программирования, имея в каждом свои особенности. В этой статье рассказано об элементах функционального программирования в языке Python, однако такие же приемы можно использовать и в других развитых языках. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|