Модуль Collections в Python — Высокопроизводительный контейнер типов данных
Краткий обзор модуля Collections в Python.
Mar 26, 2019 · 5 min read
Python — достаточно мощный ЯП с поддержкой модульного программирования. Модульное программирование представляет собой процесс разделения одной комплексной задачи программирования на несколько маленьких и более управляемых подзадач/ модулей. Модули напоминают кирпичики Лего, которые образуют большую задачу, если собрать их вместе.
Модульность обладает множеством преимуществ при написании кода:
Функции, модули и пакеты обеспечивают модуляризацию кода в Python.
Введение
Начнем с небольшого обзора модулей и пакетов.
Module
Встроенные модули в Python
В Python имеется бесчисленное количество встроенных модулей и пакетов практически на любой случай. Полный список можно посмотреть здесь.
Packages
Пакеты — это коллекция модулей, собранных вместе. Базовые пакеты машинного обучения — Numpy и Scipy — состоят из коллекции сотен модулей. Ниже приведен неполный список подпакетов, доступных в SciPy.
Модуль Collections
Рассмотрим несколько структур данных, представленных в этом модуле:
1. namedtuple()
Доступ к данным, хранящимся в обычном кортеже, можно получить с помощью индексов. Пример:
Не обязательно давать названия отдельным элементам, хранящимся в кортеже. В этом есть необходимость лишь в том случае, если кортеж обладает множеством полей.
Именно здесь функциональность namedtuple проявляет свои силы. Это функция для кортежей с именованными полями ( Named Fields), которую можно рассматривать как расширение встроенного типа данных tuple. Именованные кортежи задают значение для каждой позиции в кортеже, делая код более читаемым и самодокументируемым. Доступ к объектам, хранящимся в кортеже, можно получить с помощью уникального (удобного для чтения) идентификатора. Это избавляет от необходимости запоминать целочисленные индексы. Рассмотрим его реализацию.
Построение namedtuple начинается с передачи названия объекта type (fruit). Затем передается строка с пробелами между названиям каждого поля. Теперь можно обращаться к различным параметрам:
Namedtuples — эффективная для памяти опция при определении неизменяемых полей в Python.
2. Counter
Counter — это подкласс dict, который используется для подсчета объектов hashable. Элементы хранятся в качестве ключей словаря, а количество объектов сохранено в качестве значения. Рассмотрим несколько примеров с Counter.
Помимо доступных для всех словарей методов, объекты Counter поддерживают еще три дополнительных:
Возвращает количество каждого элемента. В случае, если количество элемента меньше одного, метод не выполняется.
Возвращает список наиболее повторяемых элементов и количество каждого из них. Количество элементов указывается в значении n. Если ни одно из значений не указано, возвращается количество всех элементов.
Распространенные шаблоны использования объекта Counter()
3. defaultdict
Словари — это эффективный способ хранения данных для дальнейшего извлечения, в котором данные представлены в виде неупорядоченного множества пар key:value. Ключи — это уникальные и неизменяемые объекты.
Все очень просто, когда значения представлены целыми числами или строками. Однако если они представлены в форме списков и словарей, значение (пустой список или словарь) нужно инициализировать при первом использовании ключа. defaultdict автоматизирует и упрощает этот процесс. Для лучшего понимания рассмотрим пример ниже:
Словарь Python выдает ошибку, поскольку ‘A’ на данный момент не находится в словаре. Рассмотрим тот же самый пример с использованием defaultdict.
defaultdict создает элементы, для которых нужно получить доступ (если они еще не существуют). defaultdict также является объектом-словарем и содержит те же методы, что и словарь. Разница заключается в том, что он устанавливает первый аргумент (default_factory) в качестве типа данных по умолчанию для словаря.
4.OrderedDict
OrderedDict — это подкласс словаря, в котором хранится порядок добавления ключей. При итерации упорядоченного словаря элементы возвращаются в порядке добавления их ключей. Поскольку упорядоченный словарь запоминает порядок добавления, его можно использовать в сочетании с сортировкой для создания отсортированного словаря:
Стоит отметить, что в Python 3.6 обычные словари отсортированы по добавлению, т. е. словари запоминают порядок добавления элементов.
Заключение
Модуль Collections также включает и другие полезные типы данных, такие как deque, Chainmap, UserString и т. д. Я рассказал лишь о самых используемых. Для более полного объяснения посмотрите официальную документацию Python.
Модуль коллекции Python – введение и краткий обзор collections
Модуль коллекции Python определяется как контейнер, который используется для хранения данных, например list, dict, set, tuple и т. д. Он представляет улучшение функциональности встроенных контейнеров. Модуль collections был впервые представлен в версии 2.4 Python.
Существуют несколько типов модулей коллекции.
Namedtuple()
Функция Python namedtuple() возвращает объект, подобный кортежу, с именами для каждой позиции в кортеже. Используется для устранения проблемы запоминания индекса каждого поля объекта кортежа в обычных кортежах.
OrderedDict()
Python OrderedDict() похож на объект словаря, в котором ключи поддерживают порядок вставки. Если мы попытаемся вставить ключ снова, предыдущее значение будет перезаписано для этого ключа.
Defaultdict()
Python defaultdict() определяется как объект, подобный словарю. Это подкласс встроенного класса dict. Он предоставляет все методы, предоставляемые словарем, но принимает первый аргумент как тип данных по умолчанию.
Counter()
Counter() Python – это подкласс объекта словаря, который помогает подсчитывать хешируемые объекты.
Deque()
Python deque() – это двусторонняя очередь, которая позволяет нам добавлять и удалять элементы с обоих концов.
Объекты Chainmap
Класс chainmap используется для группировки нескольких словарей вместе для создания единого списка. Связанный словарь хранится в списке и является общедоступным, и к нему можно получить доступ с помощью атрибута map. Рассмотрим следующий пример.
Объекты UserDict
UserDict действует как оболочка для объектов словаря. Доступ к словарю как к атрибуту можно получить с помощью объекта UserDict. Это обеспечивает простоту работы со словарем. Он предоставляет атрибут data – настоящий словарь, используемый для хранения содержимого класса UserDict.
Объекты UserList
UserList действует как класс-оболочка для объектов-списков. Это полезно, когда мы хотим добавить новые функции в списки. Также предоставляет атрибут data – реальный список, который используется для хранения содержимого класса User.
Объекты UserString
UserList действует как класс-оболочка для объектов списка. Доступ к словарю можно получить как к атрибуту с помощью объекта UserString, что обеспечивает простую работу со словарем. Атрибут data – реальный объект str для хранения содержимого класса UserString.
Python: коллекции, часть 1/4: классификация, общие подходы и методы, конвертация
| Часть 1 | Часть 2 | Часть 3 | Часть 4 |
|---|
Коллекция в Python — программный объект (переменная-контейнер), хранящая набор значений одного или различных типов, позволяющий обращаться к этим значениям, а также применять специальные функции и методы, зависящие от типа коллекции.
Частая проблема при изучении коллекций заключается в том, что разобрав каждый тип довольно детально, обычно потом не уделяется достаточного внимания разъяснению картины в целом, не проводятся чёткие сходства и различия между типами, не показывается как одну и туже задачу решать для каждой из коллекций в сравнении.
Вот именно эту проблему я хочу попытаться решить в данном цикле статей – рассмотреть ряд подходов к работе со стандартными коллекциями в Python в сравнении между коллекциями разных типов, а не по отдельности, как это обычно показывается в обучающих материалах. Кроме того, постараюсь затронуть некоторые моменты, вызывающие сложности и ошибки у начинающих.
Для кого: для изучающих Python и уже имеющих начальное представление о коллекциях и работе с ними, желающих систематизировать и углубить свои знания, сложить их в целостную картину.
Будем рассматривать стандартные встроенные коллекционные типы данных в Python: список (list), кортеж (tuple), строку (string), множества (set, frozenset), словарь (dict). Коллекции из модуля collections рассматриваться не будут, хотя многое из статьи должно быть применимым и при работе с ними.
ОГЛАВЛЕНИЕ:
1. Классификация коллекций
Пояснения терминологии:
Индексированность – каждый элемент коллекции имеет свой порядковый номер — индекс. Это позволяет обращаться к элементу по его порядковому индексу, проводить слайсинг («нарезку») — брать часть коллекции выбирая исходя из их индекса. Детально эти вопросы будут рассмотрены в дальнейшем в отдельной статье.
Уникальность – каждый элемент коллекции может встречаться в ней только один раз. Это порождает требование неизменности используемых типов данных для каждого элемента, например, таким элементом не может быть список.
Изменяемость коллекции — позволяет добавлять в коллекцию новых членов или удалять их после создания коллекции.
Примечание для словаря (dict):
UPD: Важное замечание от sakutylev: Для того, чтобы объект мог быть ключом словаря, он должен быть хешируем. У кортежа, возможен случай, когда его элемент является не хешируемым объектом, и соответственно сам кортеж тогда тоже не является хешируемым и не может выступать ключом словаря.
2 Общие подходы к работе с любой коллекцией
Разобравшись в классификацией, рассмотрим что можно делать с любой стандартной коллекцией независимо от её типа (в примерах список и словарь, но это работает и для всех остальных рассматриваемых стандартных типов коллекций):
2.1 Печать элементов коллекции с помощью функции print()
2.2 Подсчёт количества членов коллекции с помощью функции len()
2.3 Проверка принадлежности элемента данной коллекции c помощью оператора in
x in s — вернет True, если элемент входит в коллекцию s и False — если не входит
Есть и вариант проверки не принадлежности: x not in s, где есть по сути, просто добавляется отрицание перед булевым значением предыдущего выражения.
Для словаря возможны варианты, понятные из кода ниже:
Можно ли проверять пары? Можно!
Для строки можно искать не только один символ, но и подстроку:
2.4 Обход всех элементов коллекции в цикле for in
В данном случае, в цикле будут последовательно перебираться элементы коллекции, пока не будут перебраны все из них.
Обратите внимание на следующие моменты:
Но чаще всего нужны пары ключ(key) — значение (value).
Чтобы этого избежать подобных побочных эффектов, можно, например, итерировать копию коллекции:
2.5 Функции min(), max(), sum()
3 Общие методы для части коллекций
Объяснение работы методов и примеры:
Особые методы сравнения множеств (set, frozenset)
При равенстве множеств они одновременно и подмножество и надмножество друг для друга
4 Конвертация одного типа коллекции в другой
В зависимости от стоящих задач, один тип коллекции можно конвертировать в другой тип коллекции. Для этого, как правило достаточно передать одну коллекцию в функцию создания другой (они есть в таблице выше).
Обратите внимание, что при преобразовании одной коллекции в другую возможна потеря данны:
Дополнительные детали:
Это ограничение можно обойти, создав словарь комбинируя ключи со значениями с использованием zip():
Примечание: Самые мощные и гибкие способы — генераторы коллекций будут рассмотрены отдельно в четвертой части цикла, так как там много нюансов и вариантов использования, на которых редко заостряют внимание, и требуется детальный разбор.
Не изобретать велосипед, или Обзор модуля collections в Python
Leo Matyushkin
Это доступный «из коробки» родной модуль Python – те самые батарейки, что идут в комплекте. Уверенное владение инструментарием collections, itertools и других модулей стандартной библиотеки – одна из черт, отличающих продвинутых питонистов от новичков.
Рассмотрим на примерах самые популярные составляющие модуля collections для Python 3 (проверено на 3.6). Для начала импортируйте библиотеку:
Счётчик (Counter)
Одна из распространённых задач, для которой начинающие питонисты придумывают собственные решения, – подсчёт элементов последовательности: списка, строки символов и т. д.
Функция принимает итерируемый аргумент и возвращает словарь, в котором ключами служат индивидуальные элементы, а значениями – количества повторений элемента в переданной последовательности. Посчитаем, сколько раз встречается каждая буква в слове «абракадабра»:
Обращение к ключам происходит аналогично обычному словарю:
Если элемент отсутствовал в последовательности, при обращении по ключу счётчик не вызовет исключение, а вернет нулевое значение:
Присвоение нуля ключу не удаляет это значение, а создаёт соответствующую пару:
В качестве аргумента конструктор принимает не только последовательность, но и словарь, содержащий результаты подсчёта:
Метод elements() преобразует результаты подсчета в итератор:
Метод most_common(n) ищет n самых повторяющихся элементов. Найдём для примера три наиболее частых символа:
Счётчики складываются и вычитаются друг из друга:
Операнд & даст минимальные значения для одних и тех же подсчитываемых элементов, операнд | – максимальные:
Как видно из примера, счётчику можно передавать отрицательные значения. Однако для перечисленных операций хранятся только положительные подсчеты. Нулевые или отрицательные значения обычно приходится хранить при вычитании, что реализовано в методе subtract() :
Обратите внимание, что метод subtract() обновляет сам счётчик, а не создает новый.
Распространненные шаблоны применения Counter :
Унарные операции оставляют только положительные или отрицательные подcчёты:
Счетчик в сочетании с регулярными выражениями используется для частотного анализа текста. Давайте узнаем, какие десять слов чаще прочих встречаются в тексте «Евгения Онегина»:
Словарь со значением по умолчанию (defaultdict)
Что будет, если обратиться к словарю по ключу, которого в нем ещё нет?
Верно, исключение KeyError :
Соответствующему конструктору в качестве аргумента передается тип элемента по умолчанию:
Таким образом, для ключей, к которым происходит обращение, конструктор поставит в соответствие дефолтный элемент данного типа. В случае str – пустая строка, для целых чисел – 0 и т. д.
Можно видеть, что при таком подходе нет необходимости ни проверять наличие соответствующих ключей, ни создавать предварительно пустые списки.
Словарь с памятью порядка добавления элементов (OrderedDict)
Так как OrderedDict это упорядоченная последовательность, объекты содержат соответствующие методы, реорганизующие структуру:
Контейнер словарей (ChainMap)
При обращении к ChainMap по ключу одного из словарей, происходит поиск значения среди всех словарей, при этом нет необходимости указывать конкретный словарь:
При поиске ChainMap выводит первое найденное значение (проходя словари по очереди добавления). В том числе если в словарях несколько одинаковых ключей:
Так как ChainMap это комбинация словарей, логично, что у неё есть методы keys() и values() :
При необходимости расширить составленный ранее ChainMap можно методом new_child() :
Обратите внимание, что метод не обновляет старую структуру, а создаёт новую.
Двусторонняя очередь (deque)
Чтобы добавлять не одиночный элемент, а группу итерируемого объекта iterable используйте соответственно extend(iterable) и extendleft(iterable ).
Аналогично методу append() метод pop() для deque работает с обоих концов:
Если нужно посчитать число вхождений элемента в последовательность, применяем метод count() :
Кроме перечисленных, доступны следующие методы:
Другой шаблон применения deque – хранение последних добавленных элементов с выбрасыванием более старых. Пример компактной и быстрой реализации функции скользящего среднего:
Именованный кортеж и функция namedtuple()
namedtuple() – функция-фабрика для создания именованных кортежей. Этот тип данных похож на struct в других языках программирования:
Именованные кортежи делают код яснее – вместо индексирования составляющие объекта вызываются по явным именам. Остаётся доступной и численная индексация:
Именованные кортежи часто используются для назначения имён полей кортежам, возвращаемым модулями csv или sqlite3 :
Структура namedtuple похожа на словарь. Посредством метода _asdict можно представить те же данные в виде OrderedDict :
Чтобы вызвать значение через строковый ключ, необязательно преобразовывать namedtuple – подходит стандартная функция getattr() :
Чтобы преобразовать словарь в именованный кортеж заданного типа, достаточно распаковать его оператором ** :
Имена полей namedtuple перечислены в _fields :
С версии 3.7 можно присвоить полям значения по умолчанию.
Поскольку именованный кортеж является обычным классом Python, в него легко привнести новую функциональность или изменить старую. Например, добавим к Point расчёт гипотенузы и формат вывода данных:
Резюме
Подведём итог нашему рассказу об основных составляющих модуля collections:
А вы уже используете collections в своих проектах?
Python: коллекции, часть 2/4: индексирование, срезы, сортировка
| Часть 1 | Часть 2 | Часть 3 | Часть 4 |
|---|
Данная статья является продолжением моей статьи «Python: коллекции, часть 1: классификация, общие подходы и методы, конвертация».
В данной статье мы продолжим изучать общие принципы работы со стандартными коллекциями (модуль collections в ней не рассматривается) Python.
Для кого: для изучающих Python и уже имеющих начальное представление о коллекциях и работе с ними, желающих систематизировать и углубить свои знания, сложить их в целостную картину.
ОГЛАВЛЕНИЕ:
1. Индексирование
1.1 Индексированные коллекции
Рассмотрим индексированные коллекции (их еще называют последовательности — sequences) — список (list), кортеж (tuple), строку (string).
Под индексированностью имеется ввиду, что элементы коллекции располагаются в определённом порядке, каждый элемент имеет свой индекс от 0 (то есть первый по счёту элемент имеет индекс не 1, а 0) до индекса на единицу меньшего длины коллекции (т.е. len(mycollection)-1).
1.2 Получение значения по индексу
Для всех индексированных коллекций можно получить значение элемента по его индексу в квадратных скобках. Причем, можно задавать отрицательный индекс, это значит, что будем находить элемент с конца считая обратном порядке.
1.3 Изменение элемента списка по индексу
Поскольку кортежи и строки у нас неизменяемые коллекции, то по индексу мы можем только брать элементы, но не менять их:
А вот для списка, если взятие элемента по индексу располагается в левой части выражения, а далее идёт оператор присваивания =, то мы задаём новое значение элементу с этим индексом.
UPD: Примечание: Для такого присвоения, элемент уже должен существовать в списке, нельзя таким образом добавить элемент на несуществующий индекс.
2 Срезы
2.1 Синтаксис среза
Очень часто, надо получить не один какой-то элемент, а некоторый их набор ограниченный определенными простыми правилами — например первые 5 или последние три, или каждый второй элемент — в таких задачах, вместо перебора в цикле намного удобнее использовать так называемый срез (slice, slicing).
Следует помнить, что взяв элемент по индексу или срезом (slice) мы не как не меняем исходную коллекцию, мы просто скопировали ее часть для дальнейшего использования (например добавления в другую коллекцию, вывода на печать, каких-то вычислений). Поскольку сама коллекция не меняется — это применимо как к изменяемым (список) так и к неизменяемым (строка, кортеж) последовательностям.
Синтаксис среза похож на таковой для индексации, но в квадратных скобках вместо одного значения указывается 2-3 через двоеточие:
Особенности среза:
Поэтому, например, mylist[::-1] не идентично mylist[:0:-1], так как в первом случае мы включим все элементы, а во втором дойдем до 0 индекса, но не включим его!
Примеры срезов в виде таблицы:
2.2. Именованные срезы
Чтобы избавится от «магических констант», особенно в случае, когда один и тот же срез надо применять многократно, можно задать константы с именованными срезами с пользованием специальной функции slice()()
Примечание: Nonе соответствует опущенному значению по-умолчанию. То есть [:2] становится slice(None, 2), а [1::2] становится slice(1, None, 2).
2.3 Изменение списка срезом
Важный момент, на котором не всегда заостряется внимание — с помощью среза можно не только получать копию коллекции, но в случае списка можно также менять значения элементов, удалять и добавлять новые.
Проиллюстрируем это на примерах ниже:
2.4 Выход за границы индекса
Обращение по индексу по сути является частным случаем среза, когда мы обращаемся только к одному элементу, а не диапазону. Но есть очень важное отличие в обработке ситуации с отсутствующим элементом с искомым индексом.
Обращение к несуществующему индексу коллекции вызывает ошибку:
А в случае выхода границ среза за границы коллекции никакой ошибки не происходит:
Примечание: Для тех случаев, когда функционала срезов недостаточно и требуются более сложные выборки, можно воспользоваться синтаксисом выражений-генераторов, рассмотрению которых посвещена 4 статья цикла.
3 Сортировка элементов коллекции
Сортировка элементов коллекции важная и востребованная функция, постоянно встречающаяся в обычных задачах. Тут есть несколько особенностей, на которых не всегда заостряется внимание, но которые очень важны.
3.1 Функция sorted()
Мы может использовать функцию sorted() для вывода списка сортированных элементов любой коллекции для последующее обработки или вывода.
Пример сортировки списка строк по длине len() каждого элемента:
3.2 Функция reversed()
Функция reversed() применяется для последовательностей и работает по другому:
Обратите внимание: Частая ошибка начинающих, которая не является ошибкой для интерпретатора, но приводит не к тому результату, который хотят получить.
3.4 Особенности сортировки словаря
В сортировке словаря есть свои особенности, вызванные тем, что элемент словаря — это пара ключ: значение.
UPD: Так же, не забываем, что говоря о сортировке словаря, мы имеем ввиду сортировку полученных из словаря данных для вывода или сохранения в индексированную коллекцию. Сохранить данные сортированными в самом стандартном словаре не получится, они в нем, как и других неиндексированных коллекциях находятся в произвольном порядке.
Отдельные сложности может вызвать сортировка словаря не по ключам, а по значениям, если нам не просто нужен список значений, и именно выводить пары в порядке сортировки по значению.
Для решения этой задачи можно в качестве специальной функции сортировки передавать lambda-функцию lambda x: x[1] которая из получаемых на каждом этапе кортежей (ключ, значение) будет брать для сортировки второй элемент кортежа.
UPD от ShashkovS: 3.5 Дополнительная информация по использованию параметра key при сортировке
Допустим, у нас есть список кортежей названий деталей и их стоимостей.
Нам нужно отсортировать его сначала по названию деталей, а одинаковые детали по убыванию цены.
Перед тем, как сравнивать два элемента списка к ним применялась функция prepare_item, которая меняла знак у стоимости (функция применяется ровно по одному разу к каждому элементу. В результате при одинаковом первом значении сортировка по второму происходила в обратном порядке.
Чтобы не плодить утилитарные функции, вместо использования сторонней функции, того же эффекта можно добиться с использованием лямбда-функции.
UPD от ShashkovS: 3.6 Устойчивость сортировки
Допустим данные нужно отсортировать сначала по столбцу А по возрастанию, затем по столбцу B по убыванию, и наконец по столбцу C снова по возрастанию.
Если данные в столбце B числовые, то при помощи подходящей функции в key можно поменять знак у элементов B, что приведёт к необходимому результату.
А если все данные текстовые? Тут есть такая возможность.
Дело в том, что сортировка sort в Python устойчивая (начиная с Python 2.2), то есть она не меняет порядок «одинаковых» элементов.
Поэтому можно просто отсортировать три раза по разным ключам:











