Вот уже несколько лет функциональное программирование набирает популярность. Это, конечно, не значит, что люди забрасывают свои старые языки и ООП и массово переходят на Haskell, Lisp или Erlang. Нет. Функциональная парадигма проникает в наш код через лазейки мультипарадигменных языков, а вышеупомянутые языки чаще служат флагами в этом наступлении, чем используются непосредственно.
Я собирался продолжить в том же духе и во второй части статьи представить свою библиотеку, добавляющую пару функциональных трюков в python, но потом понял, что фокус моей библиотеки не на функциональном программировании, а на практичности. На этом я и сосредоточюсь, приведу несколько жизненных примеров полезности funcy.
Разработка funcy началась с попытки собрать в кучу несколько утилит для манипулирования данными и реже функциями, поэтому большинство моих примеров будут сосредоточены именно на этом. Возможно, некоторые (или многие) примеры покажутся тривиальными, но удивительно сколько времени могут сэкономить такие простые функции и насколько более выразительным они могут сделать ваш код.
Я пройдусь по нескольким типичным задачам, которые встречаются в питоньей практике, и несмотря на свою незамысловатость, вызывают постоянные вопросы. Итак, поехали.
Несложные манипуляции с данными
- Объединить список списков. Традиционно я делал это таким образом:
| |
Все они неплохи, но требуют либо лишних телодвижений: импорты и дополнительные вызовы, либо накладывают ограничения: объединять можно только списки со списками и туплы с туплами, для суммы нужно ещё знать заранее какой тип придёт. В funcy это делается так:
| |
cat() объединяет список списков, кортежей, итераторов да и вообще любых итерируемых в один список. Если нужно объединить списки результатов вызова функции, то можно воспользоваться mapcat(), например:
| |
разберёт все строки в текстах в один плоский список. Для обеих функций есть ленивые версии: icat() и imapcat().
- Сложить несколько словарей. В питоне есть несколько неуклюжих способов объединять словари:
| |
Я всегда удивлялся почему их нельзя просто сложить? Но имеем то, что имеем. В любом случае, с funcy это делается легко:
| |
Но merge() и join() могут объединять не только словари, они работают практически для любых коллекций: словарей, упорядоченных словарей, множеств, списков, кортежей, итераторов и даже строк.
- Захват подстроки с помощью регулярного выражения. Обычно это делается так:
| |
С funcy это превращается в:
| |
Если это не кажется вам достаточно впечатляющим, то взгляните на это:
| |
Отступление про импорты и практичность
Как вы могли заметить, я импортирую функции напрямую из funcy, не используя какие-либо подпакеты. Причина, по которой я остановился на таком интерфейсе, — практичность; было бы довольно занудным требовать от всех пользователей моей библиотеки помнить откуда нужно импортировать walk() из funcy.colls или funcy.seqs, кроме того, многострочные импорты в начале каждого файла и без меня есть кому набивать.
Дополнительным преимуществом такого решения является возможность просто написать:
| |
И наслаждаться всеми функциональными прелестями и удобством, что приносит funcy, более не возвращаясь в начало файла за добавкой. Что ж, теперь, когда вы знаете где лежит всё добро, я больше не буду явно указывать импорты из funcy. Продолжим.
Кое-какие более функциональные штучки
Мы уже видели пару примеров использования функций высшего порядка — re_finder() и partial(). Стоит добавить, что сама функция re_finder() является частичным применением re_find() созданным для удобства применения в map() и ей подобных. И естественным образом, с filter() удобно использовать re_tester():
| |
Отлично, мы можем задать несколько предикатов, таких как is_private(), и фильтровать атрибуты объекта по ним:
| |
Но, что если мы хотим получить список публичных атрибутов или приватных констант, что-то задействующее комбинацию предикатов? Легко:
| |
Для удобства также есть функция, дополняющая filter():
| |
Надеюсь все утолили свой функциональный аппетит, потому пора перейти к чему-нибудь менее абстрактному.
Работа с коллекциями
Кроме утилит для работы с последовательностями, коих много больше, чем я тут описал, funcy также помогает работать с коллекциями. Основу составляют функции walk() и select(), которые аналогичны map() и filter(), но сохраняют тип обрабатываемой коллекции:
| |
Эта пара функций подкрепляется набором для работы со словарями: walk_keys(), walk_values(), select_keys(), select_values():
| |
Последний пример из этой серии будет использовать сразу несколько новых функций: silent() — глушит все исключения, бросаемые оборачиваемой функцией, возвращая None; compact() — убирает из коллекции значения None; walk_values() — обходит значения переданного словаря, конструируя новый словарь с значениями, преобразованными переданной функцией. В целом эта строка выбирает словарь целочисленных параметров из параметров запроса:
| |
Манипулирование данными
О! Мы добрались до самого интересного, сюда часть примеров я включил просто потому, что они кажутся мне клёвыми. Хотя, если честно, я делал это и выше. Сейчас мы будем разделять и группировать:
| |
Собирать плоские данные во вложенные структуры:
| |
И ещё пара примеров, просто до кучи:
| |
Не просто библиотека
Возможно, некоторые из вас встретили знакомые функции из Clojure и Underscore.js (кстати, пример с Шекспиром нагло содран из документации последней), — ничего удивительного, я во многом черпал вдохновение из этих источников. При этом я старался следовать питоньему стилю, сохранять консистентность библиотеки и нигде не жертвовать практичностью, поэтому не все функции полностью соответствуют своим прототипам, они скорее соответствуют друг другу и стандартной библиотеке.
И ещё одна мысль. Мы привыкли называть языки программирования языками, при этом редко осознаём, что синтаксические конструкции и стандартные функции — это слова этих языков. Мы можем добавлять свои слова, определяя функции, но обычно такие слова слишком специфичны, чтобы попасть в повседневный языковой словарь. Утилиты из funcy, напротив, заточены под широкую область применения, поэтому эту библиотеку можно воспринимать как расширение python, также как underscore или jQuery — расширение JavaScript. Итак, всем кто хочет пополнить свой словарный запас — добро пожаловать.
Comments also at Хабр