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

Пропустить / Проект

Иногда вам дают словарь, и вы хотите продолжить работу только с его подмножеством. Например, создавая образ, вы создаете конечную точку API отдыха и хотите вернуть только подмножество атрибутов вашей модели. С этой целью funcy предоставляет две функции, а именно omit и project.

from funcy import project, omit
data = {"this": 1, "is": 2, "the": 3, "sample": 4, "dict": 5}
# FUNCY
omitted_f = omit(data, ("is", "dict"))
# PLAIN PYTHON
omitted_p = {k: data[k] 
             for k in set(data.keys()).difference({"is", "dict"})
            }
# FUNCY
projected_f = project(data, ("this", "is"))
# PLAIN PYTHON
projected_p = {k: data[k] for k in ("this", "is")}

С funcy вам не только нужно вводить меньше символов, но и получить более читаемый и менее подверженный ошибкам код. Но зачем нам две функции? Если количество ключей, которые вы хотите сохранить, меньше, чем количество ключей, которые вы хотите удалить, выберите проект, в противном случае выберите опустить.

Сглаживание вложенных структур данных

Предположим, у вас есть вложенная структура данных, такая как список списков и списков, и вы хотите свести ее в единый список.

from funcy import lflatten
data = [1, 2, [3, 4, [5, 6]], 7, [8, 9]]
# FUNCY
flattened_f = lflatten(data)
# PLAIN PYTHON
def flatter(in_):
    for e in in_:
        if isinstance(e, list):
            yield from flatter(e)
        else:
            yield e
flattend_p = [e for e in flatter(data)]

Как видите, функциональная версия - это всего лишь одна строка, в то время как простая версия Python выглядит довольно сложной. Мне также потребовалось некоторое время, чтобы придумать решение, и я до сих пор не уверен в нем на 100%. Итак, я бы остановился на функции funcy :) Помимо версии списка l flatten, funcy также предлагает более общую версию для итераций, которая называется flatten, без префикса l. Вы найдете это для различных функций.

Разделение на куски

Предположим, у вас есть итерация из n элементов, и вы хотите разделить ее на блоки, содержащие k ‹n элементов. Последний кусок может быть меньше k, если n не делится на k. Это похоже на обучающий набор из n образцов, которые вы хотите разделить на пакеты размером k для выполнения пакетной обработки.

from funcy import lchunks
data = list(range(10100))
# FUNCY
for batch in lchunks(64, data):
    # process the batch
    pass
# PLAIN PYTHON
from typing import Iterable, Any, List
def my_chunks(batch_size:int, data:Iterable[Any])->List[Any]:
    res = []
    for i, e in enumerate(data):
        res.append(e)
        if (i + 1) % batch_size == 0:
            yield res
            res = []
    if res:
        yield res
        
for batch in my_chunks(64, data):
    # process the batch
    pass

Обратите внимание, что я использовал версию lchunks для разделения списка, а не более общую версию chunks для разделения итераций. Все, что вам нужно сделать, это передать желаемый размер пакета / фрагмента и итерацию, которую вы хотите разделить. Существует еще одна функциональная функция, называемая partition, которая возвращает только те фрагменты, которые содержат ровно k элементов. Следовательно, он пропустит последний, если n не делится на k.

Объединение нескольких словарей

Предположим, у вас есть несколько словарей, в которых хранятся данные за разные даты, но один и тот же объект. Ваша цель - объединить все эти словари в один и объединить данные из одинаковых ключей с помощью определенной функции. Здесь пригодится функция merge_with из funcy. Вам просто нужно передать функцию слияния и все словари, которые вы хотите объединить.

from funcy import merge_with, lcat
d1 = {1: [1, 2], 2: [4, 5, 6]}
# FUNCY VERSION
merged_f = merge_with(lcat, d1,d2)
# PYTHON VERSION
from itertools import chain
from typing import Callable, Iterable, Any, Dict
def _merge(func: Callable[[Iterable], Any], *dics:List[Dict])->Dict:
    # Get unique keys
    keys = {k for d in dics for k in d.keys()}
    return {k: func((d[k] for d in dics if k in d)) for k in keys}
merged_p = _merge(lambda l: list(chain(*l)), d1, d2)

Также существует функция join_with, которая похожа на merge_with, но вместо передачи каждого словаря в качестве одного аргумента вы передаете итерацию словарей. Да, и я «случайно» внедрил другую функциональную функцию lcat,, которая объединяет различные списки в один.

Кэшированное свойство

И последнее, но не менее важное: что-то совершенно другое, но очень полезное; декоратор cached_prope rty. Как следует из названия, он позволяет создавать свойства, которые выполняются только один раз, а затем кэшировать результат этого выполнения и возвращать этот результат во всех последующих вызовах. Я часто использую это при создании классов наборов данных, так как это дает мне очень чистый и читаемый интерфейс, сокращая время загрузки.

from funcy import cached_property
import pandas as pd
# Funcy Version
class DatasetsF:
    @cached_property
    def movies(self) -> pd.Dataframe:
        return pd.read_csv("the_biggest_movie_file.csv")
# PYTHON VERSION
class DatasetsP:
    def __init__(self):
        self._movies = None
@property
    def movies(self) -> pd.Dataframe:
        if self._movies is None:
            self._movies = pd.read_csv("the_biggest_movie_file.csv")
        return self._movies

Заключение

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