Модуль collections на примерах

автор

Модуль collections содержит специализированный контейнер типов данных, который может быть использован для замены контейнеров общего назначения Python (dict, tuple, list, и set). Мы изучим следующие части этого замечательного модуля:

  • ChainMap
  • defaultdict
  • deque
  • namedtuple
  • OrderedDict

Также существует наследованный модуль коллекций под названием abc, или Abstract Base Classes. Мы рассмотрим его в другой раз. Давайте начнем с контейнера ChainMap!

ChainMap

ChainMap – это класс, который дает возможность объединить несколько сопоставлений вместе таким образом, чтобы они стали единым целым. Если вы обратитесь к документации, то увидите, что данный класс принимает **maps*.

Это значит, что ChainMap будет принимать любое количество сопоставлений или словарей и превращать их в единое обновляемое представление. Давайте взглянем на пример, чтобы вы могли увидеть, как это работает:

Здесь мы импортировали ChainMap из модуля collections. Затем мы создали три словаря Python. Далее, мы создали экземпляр ChainMap, передав эти три словаря. В конце мы попытались получить доступ к одному из ключей в нашем ChainMap. После этого, ChainMap пройдет через каждое сопоставление, чтобы увидеть, существует ли данный ключ и имеет ли он значение. Если это так, тогда ChainMap вернет первое найденное значение, которое соответствует ключу. Это весьма полезно в тех случаях, когда вам нужно установить настройки по умолчанию.

Давайте представим, что нам нужно создать приложение, которое имеет определенные настройки по умолчанию. Приложение также будет знать о переменных среды операционной системы. Если существует переменная среды, которая соответствует значению ключа, который расположен в нашем приложении по умолчанию, тогда среда переопределит наши настройки по умолчанию. Теперь давайте представим, что мы можем передавать аргументы нашему приложению. Эти аргументы имеют преимущество над средой и настройками по умолчанию. Это тот случай, когда ChainMap представлен во всей красе. Давайте взглянем на пример, который основан на документации Python:

Давайте немного притормозим. Здесь мы импортировали модуль Python argparse совместно с модулем os. Мы также импортировали ChainMap.Next, простую функцию со слегка нелепыми настройками. Я видел, что эти настройки используются в некоторых популярных роутерах. Далее, мы устанавливаем наш парсер аргументов, и указываем ему, как именно он будет обрабатывать определенные параметры командной строки. Обратите внимание на то, что argparse не предоставляет способ получения объектов словаря или его аргументов, так что мы используем dict для извлечения того, что нам нужно. Здесь мы задействуем встроенный инструмент Python vars. Если вы вызовете его без аргументов, то vars будет вести себя как встроенный locals. Но если передать его объекту, то vars будет выступать в роли эквивалента свойству объекта __dict__. Другими словами, vars(args) равен args.__dict__. В конце мы создаем наш ChainMap, передав аргументы нашей командной строке (если таковые имеются), затем переменные среды и, наконец, настройки. В конце кода, мы пытаемся вызвать нашу функцию, затем устанавливаем переменные среды и снова делаем вызов. Запустите его и увидите, что в выдаче будет admin, и, как и ожидалось, test. Теперь попробуем вызвать скрипт с аргументом командной строки:

После запуска кода, я получил mike дважды. Это связанно с тем, что аргумент нашей командной строки переопределяет все остальное. Не важно, что мы установили среду, так как наш ChainMap смотрит на аргументы командной строки в первую очередь, затем на все остальное. Теперь вы знаете, как использовать ChainMaps, так что мы можем перейти к Counter!

Counter

Модуль collections также предоставляет нам небольшой аккуратный инструмент, который поддерживает быстрый и удобный в пользовании калькулятор. Этот инструмент называется Counter. Вы можете запустить его против большинства итерируемых. Давайте попробуем взглянуть на него в строке.

В данном примере мы импортировали Counter из модуля collections и передали его строке. Это возвращает нам объект Counter, который является наследуемым классом словаря Python. Когда мы запускаем эту же команду, но назначаем её счетчик переменной, чтобы доступ к словарю был несколько проще. В данном случае, мы видим, что буква “u” встречается три раза в нашем примере. Класс Counter предоставляет несколько методов, которые могут вас заинтересовать. Например, вы можете вызывать элементы, которые будут выполнять итерацию над элементами, расположенными в словаре, но в произвольном порядке. Вы можете подумать, что эта функция является своего рода скремблером, так как выдача в этом случае представлена как скремблированная версия строки.

Еще один полезный метод это most_common. Вы можете спросить Counter о том, какие объекты являются наиболее распространенными, передав число, отображающее наиболее часто повторяющие объекты “n”:

Здесь мы попросили наш Counter выяснить, какие два объекта являются наиболее повторяемыми. Как вы видите, мы получили список кортежей, в котором указано, что “u” встречается 3 раза, а “s” – два раза. Еще один метод, который я хотел бы рассмотреть, это метод subtract.

Метод subtract принимает итерируемые или отражения и использует этот аргумент для вычета. Это немного проще понять, если взглянуть на код:

Здесь мы создали заново наш первый счетчик и вывели его, чтобы узнать, что же в нем находится. Далее мы создали второй объект Counter. Наконец, мы вычли второй счетчик из первого. Если вы внимательно рассмотрите выдачу в конце, вы увидите, что количество букв для пяти объектов было уменьшено на одну. Как я заметил в начале раздела, вы можете использовать Counter против любых итерируемых или сопоставлений, так что вам не нужно использовать только строки. Вы можете также передать его кортежам, словарям и спискам! Попробуйте на практике, чтобы увидеть, как он работает с разными типами данных:
Теперь мы готовы к тому, чтобы перейти к defaultdict!

defaultdict

Модуль collections содержит удобный инструмент под названием defaultdict. Это наследуемый класс Python dict, который принимает default_factory как первичный аргументы. Тип default_factory — это обычный тип Python, такой как int или list, но вы также можете использовать функцию или лямбду. Давайте начнем с создания обычного словаря Python, который считает, сколько раз было использовано каждое слово в предложении:

Если вы запустите этот код, вы увидите выдачу, которая соответствует следующему:

Давайте попробуем сделать то же самое, но с defaultdict.

Вы обнаружите, что этот код намного проще. Это связано с тем, что defaultdict автоматически назначает ноль как значение любому ключу, который еще не имеет значения. Мы добавили одно, так что теперь в нем больше смысла, и оно также будет увеличиваться, если слово повторяется в предложении несколько раз в предложении.

Давайте попробуем использовать тип списка в качестве нашей фабрики по умолчанию. Сначала мы начнем с обычного словаря так же, как мы делали это раньше:

Этот пример основан на одном коде, который я написал несколько лет назад. В основном я читал файл строка за строкой, и мне нужно было получить номер аккаунта и сумму платежа, а также отслеживать их. При этом сделать это таким образом, чтобы я смог подвести итоги каждой учетной записи. Мы опустим часть с суммированием в нашем примере. Если вы запустите этот код, вы получите выдачу, на подобие следующей:

Теперь мы меняем этот код, используя defaultdict:

И снова, этот код разрывает условную логику if/else, что упрощает данный код. Давайте рассмотрим выдачу вышеописанного кода:

Здесь хранится кое-что интересное. Давайте попробуем использовать еще и лямбду в качестве default_factory.

Здесь мы создали defaultdict, который назначает ‘Monkey’ в качестве значения по умолчания любому ключу. Мы установили в ‘Tiger’, далее, следующий ключ мы не устанавливаем вообще. Если выведите второй ключ, вы увидите, что он был назначен как ‘Monkey’. В случае, если вы еще не поняли, практически невозможно вызвать ошибку KeyError, пока default_factory установлен в чем-то, что имеет смысл. В документации отмечают, что если вы устанавливаете default_factory в None, то вы получите KeyError. Давайте посмотрим, как это работает:

В этом случае мы просто создали очень кривой defaultdict. Он не может назначать значения по умолчанию нашему ключу, так что вместо этого он выдает KeyError. Конечно, с тех пор как наследованным классом является dict, мы можем только установить ключ в какое-нибудь значение, и это сработает.

deque

В соответствии с документацией Python, deque – это обобщение стеков и очередей. Они являются контейнером замен для списка Python. Они защищены от потоков и поддерживают эффективность соединения памяти, а также сноски с обеих сторон deque. Список оптимизирован под быстрые операции с фиксированной длиной. За всеми подробностями можете обратиться к документации Python. Наш deque поддерживает аргумент maxlen, который устанавливает границы для deque. В противном случае deque будет расти до произвольных размеров. Когда ограниченный deque заполнен, любые новые объекты, которые были добавлены, вызовут такое же количество элементов, которые выскочат с другого конца. Вот основное правило: если вам нужно что-то быстро дописать или вытащить, используйте deque. Если вам нужен быстрый случайный доступ, используйте list. Давайте уделим пару минут, и посмотрим, как мы можем создавать и использовать deque.

Здесь мы импортируем deque из нашего модуля collections, а также модуль string. Для того, чтобы создать экземпляр deque, нам нужно передать его итерируемой. В данном случае, мы передали его string.ascii_lowercase, и получили список всех строчных букв в алфавите. Наконец, мы сделали цикл над deque и вывели каждый объект. Теперь давайте взглянем на несколько методов, которыми обладает deque.

Давайте устроим небольшой разбор полетов. Сначала мы разместили строку в правом краю deque. Далее разместили другую строку в левом краю deque. Наконец, мы вызываем rotate в нашем deque и передаем его единице, что заставляет его сместиться один раз в право. Другими словами, это заставляет один объект сместиться с правой части на фронтальную. Вы можете передать ему отрицательное число, чтобы происходило то же самое, но с левой стороны. Давайте закончим этот раздел и взглянем на пример, основанный на документации Python:

Этот код работает по схожему принципу с программой-хвостом Linux. Здесь мы передаем имя файла нашему скрипту вместе с n количеством строк, которые мы хотим вернуть. Наш deque ограничен той или иной цифрой, которую мы указываем как n. Это значит, что как только deque заполнится, когда новые строки прочитаны и добавлены в deque, старые строки выпадают из другого конца и отбрасываются. Я также завернул открываемый в операторе файл в простой обработчик исключений, так как очень легко выполнить передачу по неверному пути. Таким образом, мы поймаем несуществующие файлы, к примеру. Теперь мы готовы идти дальше и приступить к изучению namedtuple.

namedtuple

В данном разделе мы сфокусируемся на namedtuple, который можно использовать для замены кортежей Python. Разумеется, namedtuple действует не так примитивно, как может показаться на первый взгляд. Я видел некоторых программистов, которые используют его как struct. Если вы не работали с языком, в котором есть struct, тогда нужно объяснить немного подробнее. Когда мы говорим о struct, мы имеем ввиду сложный тип данных, который группирует список переменных под одним именем. Давайте взглянем на пример того, как создается namedtuple, чтобы понять принцип его работы:

Здесь мы импортировали namedtuple из модуля collections. Далее мы вызываем namedtuple, который вернет новый наследуемый класс кортежа, но с названными полями. Так что фактически мы создали новый класс кортежа. Обратите внимание на то, что у нас имеется странная строка в качестве второго аргумента. Это список свойств, который мы хотим создать, разделенный пробелами. Теперь, когда у нас появился чудесный новый класс, давайте создадим его экземпляр! Как вы видите выше, это делается так же, как когда мы создаем объект auto_parts. Теперь мы можем дать auto_parts доступ к различным предметам, используя точечную нотацию, так как теперь они являются свойствами нашего класса Parts. Одно из преимуществ использования namedtuple над обычным кортежем заключается в том, что вам не нужно отслеживать индекс каждого объекта, так как каждый объект назван и доступен через свойство класса. Давайте взглянем на разницу на примере кода:

В данном коде мы создали обычный кортеж и передали доступ к цене на двигатель автомобиля, указав Python необходимый нам индекс. Как альтернативный способ, мы можем также извлечь все что есть из кортежа используя множественное назначение. Лично я предпочитаю в таких случаях использовать namedtuple, так как это проще для мозга, и вы можете использовать метод dir() для проверки кортежа и нахождения его свойств. Попробуйте сами, и посмотрите, что у вас выйдет. Как-то раз я искал способ конвертировать словарь Python в объект и наткнулся на код, который делает что-то вроде этого:

Это немного странный код, так что давайте разберем его по кусочкам. В первой строке мы импортируем namedtuple, как и раньше. Далее, мы создаем словарь Parts. Пока все логично. Теперь переходим к странной части. Здесь мы создаем наш класс namedtuple и называем его ‘Parts’. Второй аргумент – это список ключей из нашего словаря. Последняя часть этой странной части кода: (Parts)**. Двойная звездочка означает, что мы вызываем наш класс при помощи ключевых аргументов, в нашем случае это словарь. Мы можем разделить эту строку на две части, для большей гигиеничности:

Здесь мы делаем то же, что и раньше, за исключением того, что сначала мы создаем класс, затем мы вызываем этот класс при помощи словаря для создания объекта. Еще один момент, который я хотел бы прояснить: namedtuple также принимает аргументы verbose и rename.

  • Аргумент verbose – это флаг, который выводит определение класса непосредственно перед его построением, если вы укажете его значение как True.
  • Аргумент rename весьма полезен в тех случаях, когда вы создаете namedtuple из базы данных, или какой-нибудь другой системы, которую ваша программа не контролирует для автоматического переименования свойств для вас.

С этого момента вы должны быть достаточно ознакомлены с namedtuple для личного пользования, так что теперь мы перейдем к OrderedDict.

OrderedDict

Модуль Python collections имеет еще один замечательный наследуемый класс dict под названием OrderedDict. Как подразумевается в его названии, этот словарь отслеживает порядок ключей после их добавления. Если вы создадите обычный dict, вы заметите, что данные в нем неупорядоченные:

Каждый раз, когда вы выводите этот код, порядок может быть разный. Иногда бывают ситуации, когда вам нужно выполнить цикл над ключами в вашем словаре в определенном порядке. Например, в моей практике был случай, когда мне нужно было отсортировать ключи, чтобы я мог перебрать их по порядку. Чтобы сделать это, вы можете выполнить следующее:

Результат

Давайте создадим экземпляр OrderedDict, используя оригинал dict, но на этапе создания мы отсортируем ключи в этом словаре:

Результат

Здесь мы создали наш OrderedDict, сортируя его при помощи встроенной функции Python sorted. Функция sorted берет объекты словаря, который является списком кортежей, отображающих пары ключей словаря. Данная функция сортирует их, затем передает в OrderedDict, который сохраняет указанный порядок. Таким образом, когда мы выводим ключи и значения, они будут в том порядке, который мы и ожидаем. Если вы создадите цикл над обычным словарем (с неотсортированным списком ключей), порядок будет меняться каждый раз. Обратите внимание на то, что если вы добавляете новые ключи, они будут расположены в конце OrderedDict, без автоматической сортировки. Также стоит отметить, что когда вы сравниваете два OrderedDicts, они не только будут тестировать объекты на равенство, но также на то, корректен ли указанный порядок. Обычный словарь только смотрит на содержимое словаря, не обращая никакого внимание на порядок этого содержимого. Наконец, OrderedDicts содержит два новых метода в Python 3: popitem и move_to_end.

  • Метод popitem возвращает и удаляет пары (ключей или объектов).
  • Метод move_to_end двигает существующий ключ в любой конец OrderedDict. Объект будет смещен в правый угол, если последний аргумент OrderedDict указан как True (это по умолчанию), или в начало, если он указан как False.

Любопытно, но OrderedDicts поддерживает обратную итерацию, используя встроенную функцию reversed:

Результат

Весьма точно, однако, вероятно вам такой функционал не понадобится.

Подведем итоги

Мы поработали с большим объемом материала в данной статье. Вы научились использовать defaultdict и OrderedDict. Мы также узнали много о наследуемом классе list – deque. Наконец, мы узнали, как использовать namedtuple в тех или иных случаях. Надеюсь, данная информация поможет вам в работе в будущем.

Вам может быть интересно