time

Python: Lambda функции

Первоисточник: Python: Lambda Functions

Мой вольный перевод статьи о работе с lambda функциями в Python и обработке списков функциями filter(), map() и reduce().

Python разрешает создание анонимных функций (например, функции, которые не связаны с именем) во время выполнения, используя конструкцию “lambda”. Это не тоже самое что lambda в функциональных языках программирования, но это очень сильная концепция, что хорошо интегрирована в Питон и очень часто используется в связке с обычными функциями типа filter(), map() и reduce().

Следующий кусок кода показывает разницу между обычным определением функции (”f”) и lambda функции (”g”)

>>> def f (x): return x**2
...
>>> print f(8)
64
>>>
>>> g = lambda x: x**2
>>>
>>> print g(8)
64

Как видно, f() и g() делают одно и тоже и могут использоваться в одних и тех же местах. Заметьте, что определение lamda функции не включает оператор “return” — эта конструкция всегда содержит выражении, результат которого возвращается. Также отметьте, что можете использовать определение lambda функция везде, где ожидается функция и нет надобности присваивать значение переменной.

Следующий фрагмент кода демонстрирует использование lambda функции. Заметьте, что вы должны использовать Python 2.2 или новее, что бы использовать внутреннею область видимости (в предыдущих версиях надо передать “n” через аргумент по-умолчанию, что бы пример заработал).

>>> def make_incrementor (n): return lambda x: x + n
>>>
>>> f = make_incrementor(2)
>>> g = make_incrementor(6)
>>>
>>> print f(42), g(42)
44 48
>>>
>>> print make_incrementor(22)(33)
55

Выше приведенный код определяет функцию “make_inrementor” которая создает анонимную функцию на лету и возвращает ее. Возвращенная функция увеличивает свой аргумент на значение, которое было определенно когда она была создана.

Вы теперь можете создавать множество определений инкрементирующих функций и назначить их переменным, затем использовать их независимо друг ото друга. Как демонстрирует последний оператор, вам даже не надо задавать функцию — вы можете просто использовать ее немедленно и забыть про нее когда она больше не понадобиться.

Следующий пример расширяет выше приведенное.

>>> foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]
>>>
>>> print filter(lambda x: x % 3 == 0, foo)
[18, 9, 24, 12, 27]
>>>
>>> print map(lambda x: x * 2 + 10, foo)
[14, 46, 28, 54, 44, 58, 26, 34, 64]
>>>
>>> print reduce(lambda x, y: x + y, foo)
139

Сначала мы создаем простой список целых чисел, затем мы используем стандартные функции filter(), map() и reduce() для разных манипуляций со списком. Каждая из трех функций ожидает два аргумента: функцию и список.

Конечно мы можем где-то определить отдельные функции и потом использовать эти функции как аргументы в filter() и это фактически хорошая идея если мы будем использовать эти функции несколько раз или если функции сложные для написания в одну строчку. Однако если они нам нужны только один раз или они несложные (например они содержат только одно выражение, как в примере выше), более удобней использовать конструкцию lambda для создания (временных) анонимных функций и передачи их filter(). Это создает очень компактный и по прежнему читаемый код.

В первом примере, filter() вызывает lambda функцию для каждого элемента списка и возвращает новый список, который содержит только те элементы для которых функция возвращает “true”. В этом случае, мы получаем список всех элементов которые кратные 3. Выражение x % 3 == 0 вычисляет остаток от деления x на 3 и сравнивает результат с 0 (которые равняется true если х делиться ровно на 3).

Во втором примере, функция map() используется для преобразования нашего списка. Переденая функция вызываеться для каждого элемента в оригинальном списке и создается новый список, который содержит возвращаемое значение с lambda функции. В этом случаи вычисляется 2 * x + 10 для каждого элемента.

Наконец, reduce() – что-то особенное. Функция обработки должна принимать два аргумента (мы их называем x и y). Функция вызывается для первых двух элементов из списка, затем для результата предыдущих вычислений и третего и т.д. пока не будут обработаны все элементы списка. Это означает, что функция будет вызвана n-1 раз если список содержит n элементов. Возвращенное значение последнего вызова есть результат операции reduce(). В выше приведенном примере просто суммируются аргументы.

Следующий пример показывает один из путей вычислений простых чисел в Python (хотя не самый эффективный):

>>> nums = range(2, 50)
>>> for i in range(2, 8):
...     nums = filter(lambda x: x == i or x % i, nums)
...
>>> print nums
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

Как это работает? Сначала мы инициализируем список “nums” числами от 2 до 49. Затем цикл “for” проходит по всем возможным делителям, т.е. значение “i” проходит от 2 до 7. Естественно, все числа что кратны этим делителям не могут быть простыми числами, итак мы используем функцию filter() для удаления их из списка (Этот алгоритм называется решето Эратосфена).

В выше приведенном случаи функция filter() просто указывает: оставить элемент в списке если он равен i или если остается не нулевой остаток при делении на i. Иначе удалить его из списка. После того как фильтрующий цикл закончиться останутся только простые числа. Я не знаю другой язык в котором вы сможете делать похожие вещи с встроенными возможностями также компактно и наглядно читающимися как в Python (исключая функциональные языки программирования).

Заметка: Функция range() просто возвращает список, который содержит числа от x до y-1. Например, range(5, 10) вернет список [5, 6, 7, 8, 9].

В следующем примере предложение разбивается на список слов, затем создается список который содержит длину каждого слова.

>>> sentence = 'It is raining cats and dogs'
>>> words = sentence.split()
>>> print words
['It', 'is', 'raining', 'cats', 'and', 'dogs']
>>>
>>> lengths = map(lambda word: len(word), words)
>>> print lengths
[2, 2, 7, 4, 3, 4]

Я думаю этот код не требует дальнейшего пояснения, код — само-документирующий.

Конечно, это все можно записать одной строкой. Правда, это менее наглядно (хотя не так сильно).

>>> print map(lambda w: len(w), 'It is raining cats and dogs'.split())
[2, 2, 7, 4, 3, 4]

Ниже приведен пример скрипта из мира UNIX: мы хотим найти все точки монтирования в нашей файловой системе. Для этого, мы выполним внешнею команду “mount” и обработаем вывод.

>>> import commands
>>>
>>> mount = commands.getoutput('mount -v')
>>> lines = mount.split('
')
>>> points = map(lambda line: line.split()[2], lines)
>>>
>>> print points
['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']

Функция getoutput из модуля commands (который есть частью стандартной библиотеки Python) запускает переданную комаду и возвращает ее вывод как одиночную строку. Поэтому сначала мы разбиваем ее на несколько линий. После этого мы используем map() с lambda функцией которая разбивает каждую строку (по-умолчанию на пробельных символах) и возвращает третий элемент результата, который является точкой монтирования.

Снова, мы можем написать все это одним оператором, который увеличивает компактность но уменьшает читаемость:

print map(lambda x: x.split()[2], commands.getoutput('mount -v').split('
'))
['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']

Когда пишутся реальные скрипты рекомендовано разбивать сложные операторы для облегчения ихнего понимания. Также, проще будет вносить изменения.

Однако задача разбиения вывода команды на список строк очень часто встречается. Вам это понадобиться когда обрабатываете вывод внешней команды. Поэтому, это широко известная практика включать операцию разбиения в строку с getoutput, но остальное обрабатывать отдельно. Это хороший компромисс между компактностью и читаемостью:

>>> lines = commands.getoutput('mount -v').split('
')
>>>
>>> points = map(lambda line: line.split()[2], lines)
>>> print points
['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']

Еще лучше идея – написать небольшую функцию для этой задачи, которая икапсулирует операцию запуска команды и разбивки строки.

В дополнение, вы можете также использовать так называемые списочные встраивания (list comprehensions) для создания списков из других списков. Иногда это предпочтительней по-причине эффективности или читаемости. Предыдущий пример может быть переписан используя списочные встраивания:

>>> lines = commands.getoutput('mount -v').split('
')
>>>
>>> points = [line.split()[2] for line in lines]
>>> print points
['/', '/var', '/usr', '/usr/local', '/tmp', '/proc']

Во многих случаях, вы можете использовать списочные встраивания взамен map() или filter().

blog comments powered by Disqus