Бриллиант из коробки: Python-библиотека collections
Не просто библиотека, а та самая библиотека.


Dana Moskvina / Skillbox Media
Язык программирования Python, по сути, состоит из библиотек. Каждую из них можно установить, а затем импортировать в свой код и использовать как вам нужно. Наиболее полезные включаются в установку по умолчанию — они появляются на вашем компьютере вместе с собственно интерпретатором Python.
Библиотека collections — одна из таких библиотек «из коробки». В ней содержатся мощные, удобные и быстродействующие альтернативы встроенным типам данных: словарям, спискам, кортежам.
Это может пригодиться в задачах подсчёта значений, организации сбора и быстрой конвертации данных из файлов CSV и баз SQL. Говорят, что разработчики и дата-сайентисты, открывшие для себя библиотеку collections, удивляются: «Как это я раньше жил без неё?»

defaultdict: безошибочный словарь
Обычный словарь dict в Python содержит пары вида «ключ:значение». Значение из словаря можно запросить по ключу. В случае если запрашиваемого ключа в словаре нет, Python выдаст ошибку:
my_dict = {'a':1, 'b':2, 'c':3}
my_dict['a']
>>> 1
my_dict['d']
>>> Traceback (most recent call last):
>>> File "<pyshell#2>", line 1, in <module>
>>> my_dict['d']
>>> KeyError: 'd'
Поведение логичное, но не слишком удобное для работы. Словарь defaultdict из библиотеки collections поступает иначе: если запросить у такого продвинутого словаря ключ, которого в нём нет, он не обидится и не выкинет ошибку, а вместо этого:
- создаст этот ключ;
- создаст к нему значение, которое будет иметь тип, оговорённый по умолчанию («по дефолту» — отсюда, кстати, и название), — например, целое число или строку;
- и — тадамм! — вернёт вам это значение.
Строго говоря, в Python существуют стандартные методы словарей .setdefault() и .get() со сходной функциональностью, но они сложнее в использовании — надо каждый раз указывать значение по умолчанию. А здесь мы сразу задаём тип для всех таких значений и далее просто обращаемся по ключу.
Как это работает:
from collections import defaultdict
my_new_dict = defaultdict(int, my_dict)
my_new_dict
>>> defaultdict(<class 'int'>, {'a': 1, 'b': 2, 'c': 3})
my_new_dict['d']
>>> 0
my_new_dict
>>> defaultdict(<class 'int'>, {'a': 1, 'b': 2, 'c': 3, 'd': 0})
В первой строчке импортировали модуль defaultdict из библиотеки collections.
Во второй — превратили наш предыдущий словарь my_dict в словарь нового типа my_new_dict: «int», стоящий первым в скобках, означает, что значениями, создаваемыми по умолчанию, будут целые числа, а конкретно число ноль.
Далее мы выводим наш my_new_dict, чтобы посмотреть, что в нём. Интерпретатор Python показывает нам:
- тип словаря: defaultdict;
- тип значений по умолчанию: int;
- а также текущее содержимое нашего словаря. Ключа «d» в нём нет, как и в оригинальном словаре my_dict.
В следующей строке мы как раз и запрашиваем элемент с этим ключом. И, о чудо, my_new_dict выдаёт нам значение — оно равно 0.
Смотрим снова на содержимое нашего словаря и видим, что там появилась новая пара 'd': 0.
Вы наверняка догадались, что вместо int при создании словаря типа defaultdict можно писать любые стандартные типы данных: float, list, str — и даже другие словари dict. И новые элементы, создаваемые при первом обращении к ключу, будут именно этого типа: нули, пустые списки, строки и словари соответственно.
my_new_dict_2 = defaultdict(dict, my_dict)
my_new_dict_2
>>> defaultdict(<class 'dict'>, {'a': 1, 'b': 2, 'c': 3})
my_new_dict_2['e']
>>> {}
my_new_dict_2
>>> defaultdict(<class 'dict'>, {'a': 1, 'b': 2, 'c': 3, 'e': {}})
И, конечно, словарь defaultdict поддерживает все методы обычных питоновских словарей: .items(), .values(), .keys() и другие.
namedtuple: кортеж, только именованный
Кортеж (tuple, его часто называют «неизменяемым списком») в Python содержит некоторое количество элементов, к каждому из которых можно обратиться только по его индексу.
simple_tuple = (1, 'a', 123.4)
simple_tuple[1]
>>> 'a'
Для простых задач этого хватает, но как только число элементов в кортеже начинает расти, их вызов по индексу становится головной болью, потому что существенно снижает читаемость кода.
Здесь снова приходит на помощь библиотека collections, а конкретно её функция namedtuple. Это расширение над встроенными кортежами, которое даёт имя каждому элементу кортежа, а точнее — создаёт именованные поля, в которые мы, как в строку в таблице, складываем значения.
В итоге получаем что-то вроде неизменяемого (immutable) словаря, только гораздо легче читаемого.
from collections import namedtuple
fruit = namedtuple('fruit','number sort color')
apple = fruit(number=5, sort='Antonovka', color='red')
orange = fruit(number=7, sort='Navel Late', color='yellow')
Мы импортировали функцию namedtuple из collections. Затем создали конструктор именованных кортежей fruit, указав:
- общее название тех кортежей, которые мы будем создавать ('fruit');
- строку, содержащую имена полей именованного кортежа. Имена разделены в строке пробелом.
А после этого создали два именованных кортежа: apple и orange.
Теперь можно обращаться к элементам этих кортежей не по индексу, а по именам полей number, sort и color. Они являются атрибутами именованных кортежей, то есть используются через точку и без скобок.
apple.sort
>>> 'Antonovka'
orange.color
>>> 'yellow'
# обращение по индексам работает по-прежнему
apple[2]
>>> 'red'
Именованные кортежи обладают всеми свойствами простых кортежей, в том числе быстродействием и неизменяемостью, а также поддерживают их методы. В документации есть хорошие примеры использования.
Counter: посчитает всё
Counter — это разновидность словаря Python, созданная для подсчёта, что понятно из названия. Ключами в ней выступают подсчитываемые элементы, а значениями — их количества. Посмотрим на примерах, как она работает.
from collections import Counter
# строки
c = Counter('абвабббаввабббвавабг')
print(c)
>>> Counter({'б': 8, 'а': 6, 'в': 5, 'г': 1})
# списки
lst = [5,6,7,1,3,9,9,1,2,5,5,7,7]
c = Counter(lst)
print(c)
>>> Counter({5: 3, 7: 3, 1: 2, 9: 2, 6: 1, 3: 1, 2: 1})
Чаще всего в строке встречается буква «б» — 8 раз, а в списке — числа 5 и 7, по 3 раза. Подсчёт слов в предложении сводится к подсчёту элементов списка:
s = 'зима зима зима по полгода ни звоночка ни письма'
words = s.split() # вернёт список слов
Counter(words)
>>> Counter({'зима': 3, 'ни': 2, 'по': 1, 'полгода': 1, 'звоночка': 1, 'письма': 1})
Counter поддерживает все методы словарей плюс дополнительные методы elements() и most_common(n). Первый выдаёт список из элементов, количество которых больше нуля, а второй выводит список из n пар самых частых элементов в виде кортежей (элемент, количество).
c = Counter(a=3, b=2, c=1, d=-2) # указали количества элементов
sorted(c.elements())
>>> ['a', 'a', 'a', 'b', 'b', 'c']
s = 'зима зима зима по полгода ни звоночка ни письма'
words = s.split()
Counter(words).most_common(2) # 2 самых частых элемента и их количество
>>> [('зима', 3), ('ни', 2)]
Типовые примеры использования Counter():
sum(c.values()) # сумма всех количеств
c.clear() # сброс всех количеств
list(c) # список элементов
set(c) # преобразование во множество
dict(c) # преобразование в обычный словарь
c.items() # преобразование в список кортежей (элемент, количество)
Counter(dict(list_of_pairs)) # обратное преобразование из списка кортежей вида (элемент, количество)
c.most_common()[:-n-1:-1] # n самых редких элементов
c += Counter() # удаление элементов с отрицательными и нулевыми количествами
Что дальше
Удобные инструменты из библиотеки collections помогут быстро разобраться с большим количеством однотипных данных: обработать их, преобразовать и передать дальше. Используйте collections везде, где требуется скорость работы, надёжный учёт данных и хорошая читаемость кода.
На курсе «Профессия Python-разработчик» вы изучите и другие бриллианты из многочисленного семейства библиотек Python. Все они помогут вам в учёбе и дальнейшем трудоустройстве. Приходите!