Какие инструменты для функционального программирования предоставляет модуль collections в Python?

Ответ

Модуль collections в Python содержит высокопроизводительные типы данных, некоторые из которых поддерживают принципы функционального программирования, такие как иммутабельность и декларативность.

Ключевые инструменты:

  1. namedtuple: Фабрика для создания классов кортежей с именованными полями. Это повышает читаемость кода и позволяет создавать легковесные иммутабельные структуры данных, что является основой ФП.

    from collections import namedtuple
    
    # Создаем иммутабельный тип данных
    Point = namedtuple('Point', ['x', 'y'])
    p1 = Point(10, 20)
    
    print(p1.x) # Доступ по имени, а не по индексу
    # p1.x = 30 # Вызовет AttributeError, так как кортежи иммутабельны
  2. Counter: Специализированный подкласс словаря для подсчета хешируемых объектов. Упрощает агрегацию данных, позволяя писать более декларативный код.

    from collections import Counter
    
    # Декларативный подсчет элементов без циклов и условий
    data = ['apple', 'orange', 'apple', 'banana', 'orange', 'apple']
    fruit_counts = Counter(data)
    print(fruit_counts) # Counter({'apple': 3, 'orange': 2, 'banana': 1})
  3. defaultdict: Подкласс словаря, который вызывает фабричную функцию для предоставления значений по умолчанию для отсутствующих ключей. Это упрощает код группировки и агрегации, устраняя необходимость в ручных проверках ключей.

    from collections import defaultdict
    
    # Группировка данных без проверки наличия ключа
    s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
    d = defaultdict(list)
    for k, v in s:
        d[k].append(v)
    
    print(d) # defaultdict(<class 'list'>, {'yellow': [1, 3], 'blue': [2, 4], 'red': [1]})

Хотя defaultdict и Counter являются изменяемыми, они предоставляют высокоуровневые абстракции, которые помогают писать код в функциональном стиле, фокусируясь на трансформации данных, а не на деталях реализации.

Ответ 18+ 🔞

Ах, этот ваш модуль collections... Ну, не то чтобы он прям про функциональное программирование, но там есть такие штуки, что можно писать код, который не выглядит как попытка открутить себе голову отёчной рукой. Прям как будто ты не мучаешь питон, а ведёшь с ним интеллигентную беседу.

Смотри, что там есть из полезного, чтобы не изобретать велосипед с квадратными колёсами.

1. namedtuple — чтоб не путать, где что лежит. Это ж, блядь, классика! Вместо того чтобы тыкаться в кортеж пальцем и гадать, что в p[0], а что в p[1], можно дать полям имена. И главное — эта штука неизменяемая, как скала. То есть, создал — и спи спокойно, никто тебе данные по-тихому не перепишет, как в каком-нибудь списке.

from collections import namedtuple

# Объявляем тип, как будто заказываем костюм на заказ
Point = namedtuple('Point', ['x', 'y'])
p1 = Point(10, 20)

print(p1.x) # Во, красота! Не p[0], а p1.x. Прям человеческим языком.
# p1.x = 30 # А вот это уже не прокатит. AttributeError, детка. Иммутабельно, ёпта!

2. Counter — счётчик, который не надо писать самому. Представь, тебе надо посчитать, сколько раз какая хрень встречается в списке. Обычный путь — завести словарь, бежать циклом, проверять if key in dict, иначе добавлять... Короче, пиздец, а не код. А тут — одна строка, и ты уже бог статистики.

from collections import Counter

# Смотри, какая магия. Ни одного if, ни одного цикла for с кучей телодвижений.
data = ['apple', 'orange', 'apple', 'banana', 'orange', 'apple']
fruit_counts = Counter(data) # БАЦ!
print(fruit_counts) # Counter({'apple': 3, 'orange': 2, 'banana': 1})

Вот и всё, ебать. Результат как на ладони. Чистая декларативность — сказал что нужно сделать, а не как.

3. defaultdict — словарь, который не орёт на тебя KeyError. Очень часто бывает: есть список пар (ключ, значение), и нужно всё сгруппировать по ключам. С обычным словарём начинается цирк: "А есть ли уже такой ключ? Нет? Тогда создай список! А если есть? Тогда аппендни!" С defaultdict этот цирк уезжает.

from collections import defaultdict

# Задача: сгруппировать цифры по буквам.
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list) # Говорим: "Если ключа нет — создавай пустой список, не стесняйся".
for k, v in s:
    d[k].append(v) # И просто добавляем. Без проверок. Без нервов.

print(d) # defaultdict(<class 'list'>, {'yellow': [1, 3], 'blue': [2, 4], 'red': [1]})

Да, defaultdict и Counter сами по себе изменяемые, это не монады какие-нибудь. Но суть-то в чём? Они дают такую высокоуровневую абстракцию, что ты перестаёшь ковыряться в мелочах вроде инициализации значений в словаре. Ты начинаешь думать о преобразовании данных, а не о том, как бы не наступить на грабли с KeyError. И в этом, блядь, их главная ценность для того, чтобы код был читаемым и понятным, почти как в том самом функциональном стиле.