Пишем чистый код на 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 👈🏻