Пишем чистый код на Python с помощью Pipes
Функции map
и filter
эффективны при работе с итерируемыми объектами. Однако одновременное их использование может сделать код не таким чистым и читаемым, как нам бы того хотелось.
arr = [1, 2, 3, 4, 5] print(list(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, arr)))) # [4, 8]
Почему бы не использовать пайпы |
, чтобы применить к итерируемому объекту (в нашем случае — к списку) сразу несколько методов?
from pipe import select, where arr = [1, 2, 3, 4, 5] print(list(arr | where (lambda x: x % 2 == 0) | select (lambda x: x * 2))) # [4, 8]
Именно это мы и можем сделать с помощью библиотеки Pipe.
Что такое Pipe?
Pipe — это библиотека Python, которая позволяет использовать пайпы (англ. pipe — трубка, канал) для передачи результатов работы одного метода другому.
Для установки Pipe достаточно воспользоваться следующей командой:
Этот инструмент позволяет сделать код чище и удобнее для восприятия, когда над одним и тем же итерируемым объектом требуется произвести целый ряд операций. Некоторые из часто нужных операций уже реализованы внутри библиотеки, поэтому применение пайпов — процесс не сложный. О самых полезных пайп-методах мы и поговорим в этой статье.
where
— фильтруем элементы в итерируемом объекте
Метод where
из библиотеки Pipe действует точно так же, как и в SQL. С его помощью мы можем отфильтровать элементы итерируемых объектов, для которых истинно заданное условие.
В приведенном ниже примере с помощью данного метода выводится список четных чисел, так как для них истинно условие x % 2 == 0
.
from pipe import where arr = [1, 2, 3, 4, 5] print(list(arr | where (lambda x: x % 2 == 0))) # [2, 4]
select
— применяем функцию к итерируемому объекту
Pipe-метод select
действует аналогично функции map
, применяя к каждому элементу итерируемого объекта заданную функцию.
В приведенном ниже примере метод select
используется для умножения каждого элемента в списке на число 2.
from pipe import select arr = [1, 2, 3, 4, 5] print(list(arr | select (lambda x: x * 2))) # [2, 4, 6, 8, 10]
Возможно, вы задались вопросом: «Зачем нужны методы select
и where
, если они делают то же самое, что и map
вместе с filter
?».
Дело в том, что Pipe-методы можно вставлять в код один за другим, просто используя символ |
. Таким образом, мы избегаем большого количества вложенных скобок и улучшаем читаемость кода.
Разглаживаем итерируемые объекты
chain
— разглаживаем один уровень
Когда один итерируемый объект вложен в другой, работать с ними становится проблематично. Можно использовать метод chain
, позволяющий раскрыть вложенные итерируемые объекты.
from pipe import chain nested = [[1, 2, [3]], [4, 5]] print(list(nested | chain)) # [1, 2, [3], 4, 5]
Полученный в примере список стал содержать меньше вложенных итерируемых объектов, однако не всегда мы можем избавиться от них полностью. В частности, вам не удастся применить метод chain
к итерируемому объекту, если он содержит хотя бы один НЕ итерируемый объект.
traverse
— рекурсивно разглаживаем итерируемые объекты
Чтобы полностью разгладить все вложения, воспользуемся методом traverse
. Для списка nested
из приведенного выше примера результат будет выглядеть следующим образом:
from pipe import chain nested = [[1, 2, [3]], [4, 5]] print(list(nested | traverse)) # [1, 2, 3, 4, 5]
Теперь попробуем использовать методы select
и traverse
одновременно, чтобы достать значения из словаря и раскрыть все вложения.
from pipe import select, traverse fruits = [ {"name": "яблоко", "price": [20, 50]}, {"name": "апельсин", "price": 40}, {"name": "грейпфрут", "price": 50}, ] print(list(fruits | select(lambda fruit: fruit["price"]) | traverse)) # [20, 50, 40, 50]
Группируем элементы в списке
Иногда необходимо распределить элементы по группам в уже существующем списке. И метод groupby
может нам в этом помочь.
В качестве примера возьмем простой список чисел и сделаем из него словарь, разделяющий эти числа на чётные и нечётные.
from pipe import groupby, select arr = [1, 2, 3, 4, 5] print(list(arr | groupby(lambda x: "Even" if x % 2 == 0 else "Odd") # [('Even', <itertools._grouper at 0x7fc90fe4cf10>), # ('Odd', <itertools._grouper at 0x7fc90fe4c4d0>)] | select(lambda x: {x[0]: list(x[1])}))) # [{'Even': [2, 4]}, {'Odd': [1, 3, 5]}]
А теперь достанем из полученных групп элементы, имеющих значение большее, чем 2.
from pipe import groupby, select, where arr = [1, 2, 3, 4, 5] print(list(arr | groupby(lambda x: "Even" if x % 2 == 0 else "Odd") # [('Even', <itertools._grouper at 0x7fc90fe4cf10>), # ('Odd', <itertools._grouper at 0x7fc90fe4c4d0>)] | select(lambda x: {x[0]: list(x[1] | where(lambda x: x > 2))}))) # [{'Even': [4]}, {'Odd': [3, 5]}]
dedup
— удаляем дубликаты из списка
from pipe import dedup arr = [1, 1, 2, 2, 3, 3, 3, 4, 5, 6, 6, 7, 9] print(list(arr | dedup)) # [1, 2, 3, 4, 5, 6, 7, 9]
В принципе, метод set
делает то же самое, но за одним исключением: dedup
является более гибким, т.к. позволяет получить уникальные элементы по любому предварительному преобразованию.
Следующий блок кода позволяет получить два элемента, один из которых меньше 5, а другой — больше или равен 5.
from pipe import dedup arr = [1, 1, 2, 2, 3, 3, 3, 4, 5, 6, 6, 7, 9] print(list(arr | dedup(lambda key: key < 5))) # [1, 5]
И напоследок используем dedup
вместе с select
и where
.
from pipe import select, where, dedup data= [ {"name": "яблоко", "count": 20}, {"name": "апельсин", "count": 40}, {"name": "грейпфрут", "count": None}, {"name": "апельсин", "count": 70}, ] print(list(fruits | dedup(key = lambda fruit: fruit["name"]) | select(lambda fruit: fruit["count"]) | where(lambda count: isinstance(count, int)))) # [20, 40]
В конечном итоге мы отсеяли элементы с одинаковыми значениями в name
и None
-значениями в count
.
Заключение
Теперь вы знаете, что представляют собой пайпы, и как с их помощью сделать свой код чище. Надеемся, что полученные знания помогут вам превращать даже самые сложные операции в короткие строки кода.
Источник: Towards Data Science
👉🏻Подписывайтесь на PythonTalk в Telegram 👈🏻