В стандартную библиотеку Python входит модуль collections
содержащий дополнительные структуры данных. Мы поговорим о некоторых и обсудим их пользу.
А конкретно:
defaultdict
OrderedDict
counter
deque
namedtuple
enum.Enum
(вне модуля; Python 3.4+)
Я использую defaultdict
время от времени. В отличие от dict
нам не нужно проверять существует ли ключ в словаре или нет. В результате мы можем писать следующий код:
from collections import defaultdictcolours = (('Yasoob', 'Yellow'),('Ali', 'Blue'),('Arham', 'Green'),('Ali', 'Black'),('Yasoob', 'Red'),('Ahmed', 'Silver'),)favourite_colours = defaultdict(list)for name, colour in colours:favourite_colours[name].append(colour)print(favourite_colours)# Вывод:# defaultdict(<type 'list'>,# {'Arham': ['Green'],# 'Yasoob': ['Yellow', 'Red'],# 'Ahmed': ['Silver'],# 'Ali': ['Blue', 'Black']# })
Другим популярным случаем использования defaultdict
является добавление элементов в список внутри словаря. Если ключ не существует в словаре, то вы упрётесь в KeyError
. defaultdict
позволяет обойти эту проблему аккуратным образом. Для начала, позвольте привести пример использования dict
с исключением KeyError
, а затем мы посмотрим на пример с defaultdict
.
Проблема:
some_dict = {}some_dict['colours']['favourite'] = "yellow"# Вызывает KeyError: 'colours'
Решение:
import collectionstree = lambda: collections.defaultdict(tree)some_dict = tree()some_dict['colours']['favourite'] = "yellow"# Работает без ошибок
Вы можете вывести в консоль some_dict
используя json.dumps
. Вот пример:
import jsonprint(json.dumps(some_dict))# Вывод: {"colours": {"favourite": "yellow"}}
OrderedDict
сохраняет элементы в порядке добавление в словарь. Изменение значения ключа не изменяет его позиции. При этом удаление и повторное добавление перенесет ключ в конец словаря.
Проблема:
colours = {"Red": 198, "Green": 170, "Blue": 160}for key, value in colours.items():print(key, value)# Вывод:# Red 198# Blue 160# Green 170## Элементы выводятся в произвольном порядке
Решение:
from collections import OrderedDictcolours = OrderedDict([("Red", 198), ("Green", 170), ("Blue", 160)])for key, value in colours.items():print(key, value)# Вывод:# Red 198# Green 170# Blue 160## Порядок элементов сохранен
Counter
позволяет подсчитывать частоту определенных элементов. К примеру, мы можем использовать его, чтобы посчитать сколько любимых цветов у каждого человека:
from collections import Countercolours = (('Yasoob', 'Yellow'),('Ali', 'Blue'),('Arham', 'Green'),('Ali', 'Black'),('Yasoob', 'Red'),('Ahmed', 'Silver'),)favs = Counter(name for name, colour in colours)print(favs)# Вывод: Counter({# 'Yasoob': 2,# 'Ali': 2,# 'Arham': 1,# 'Ahmed': 1# })
Мы также можем посчитать частоту строк в файле. Пример:
with open('filename', 'rb') as f:line_count = Counter(f)print(line_count)
deque
предлагает нам двустороннюю очередь, которая позволяет добавлять и удалять элементы с обеих сторон. Для начала, вам нужно импортировать модуль deque
из библиотеки collections
:
from collections import deque
Теперь мы можем создать экземпляр двусторонней очереди:
d = deque()
Очередь работает подобно списку в Python и имеет схожие методы. Например, вы можете:
d = deque()d.append('1')d.append('2')d.append('3')print(len(d))# Вывод: 3print(d[0])# Вывод: '1'print(d[-1])# Вывод: '3'
Мы можем отрезать элементы с обеих сторон очереди:
d = deque(range(5))print(len(d))# Вывод: 5d.popleft()# Вывод: 0d.pop()# Вывод: 4print(d)# Вывод: deque([1, 2, 3])
Мы также можем ограничить число элементов, которые может хранить очередь. Таким образом при достижении максимального числа элементов очередь начнет отрезать элементы с другого конца. Это проще объяснить на примере:
d = deque(maxlen=30)
Теперь, когда мы попытаемся добавить 31-й элемент - очередь отрежет первый элемент с другого конца. Вы также можете добавлять элементы к очереди с обоих концов:
d = deque([1,2,3,4,5])d.extendleft([0])d.extend([6,7,8])print(d)# Вывод: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])
Вы уже должны быть знакомы с кортежами. Кортеж в Python это неизменяемый список, который позволяет хранить объекты, разделенные запятой. Они практически идентичны спискам, за исключением нескольких важных особенностей. В отличие от списков, вы не можете изменить элемент кортежа. В то же время вы можете обращаться к элементам кортежа по индексам:
man = ('Ali', 30)print(man[0])# Вывод: Ali
Отлично, так что же тогда namedtuples
? Этот модуль открывает доступ к удобной структуре данных для простых задач. С помощью именованных кортежей вам не обязательно использовать индексы для обращения к элементам кортежа. Вы можете думать об именованных кортежах как о словарях, но в отличие от словарей они неизменяемы.
from collections import namedtupleAnimal = namedtuple('Animal', 'name age type')perry = Animal(name="perry", age=31, type="cat")print(perry)# Вывод: Animal(name='perry', age=31, type='cat')print(perry.name)# Вывод: 'perry'
Теперь вы можете видеть, что мы можем обращаться к элементам именованного кортежа при помощи их имени и .
(точки). Давайте чуть подробнее на этом остановимся. Именованный кортеж имеет два обязательных аргумента. Это имя самого кортежа и имена полей кортежа. В примере выше имя нашего кортежа Animal
, имена полей соответственно: name
, age
и type
. Именованный кортеж позволяет создавать само-документированные кортежи. Вы сможете легко понять код при первом же взгляде на него. И, поскольку вы не привязаны к индексам, у вас открывается больше возможностей по поддержке своего кода. Помимо этого, именованные кортежи не создают словари для каждого экземпляра, они легковесны и не требуют больше памяти чем обычные кортежи. Это делает их быстрее словарей. Тем не менее, помните, что как и в случае с обычными кортежами, именованный кортеж неизменяем. Это означает, что такой код работать не будет:
from collections import namedtupleAnimal = namedtuple('Animal', 'name age type')perry = Animal(name="perry", age=31, type="cat")perry.age = 42# Вывод: Traceback (most recent call last):# File "", line 1, in# AttributeError: can't set attribute
Вы должны использовать именованные кортежи для улучшения читаемости кода. Они обратносовместимы с обычными кортежами. Это значит, что вы можете использовать численные индексы с именованными кортежами:
from collections import namedtupleAnimal = namedtuple('Animal', 'name age type')perry = Animal(name="perry", age=31, type="cat")print(perry[0])# Вывод: perry
И последнее, вы можете сконвертировать именованный кортеж в словарь. Вот так:
from collections import namedtupleAnimal = namedtuple('Animal', 'name age type')perry = Animal(name="Perry", age=31, type="cat")print(perry._asdict())# Вывод: OrderedDict([('name', 'Perry'), ('age', 31), ...
Другой полезной структурой данных является enum
. Он доступен в модуле enum
, начиная с Python 3.4 (также в PyPI как бекпорт под именем enum34
). Enums (перечисляемый тип) это простой способ организации разных вещей.
Давайте рассмотрим именованный кортеж Animal
из прошлого примера. У него есть поле type
. Проблема в том, что его тип - строка. Это создаёт нам несколько проблем. Что если пользователь ввёл Cat
, поскольку нажал Shift? Или CAT
? Или kitten
?
Перечисление может помочь обойти эту проблему, позволив не использовать строки. Рассмотрим пример:
from collections import namedtuplefrom enum import Enumclass Species(Enum):cat = 1dog = 2horse = 3aardvark = 4butterfly = 5owl = 6platypus = 7dragon = 8unicorn = 9# Список продолжается...# Нам безразличен возраст животного, так что мы используем синонимыkitten = 1puppy = 2Animal = namedtuple('Animal', 'name age type')perry = Animal(name="Perry", age=31, type=Species.cat)drogon = Animal(name="Drogon", age=4, type=Species.dragon)tom = Animal(name="Tom", age=75, type=Species.cat)charlie = Animal(name="Charlie", age=2, type=Species.cat)# А теперь несколько тестов>>> charlie.type == tom.typeTrue>>> charlie.type<Species.cat: 1>
Так у нас куда меньше шансов допустить ошибку. При этом мы должны быть конкретны и использовать только перечисление для определения полей.
Существует три способа получения доступа к перечисляемым элементам. Например, все три метода, представленные ниже, дадут вам значения поля cat
:
Species(1)Species['cat']Species.cat
Это было короткое погружение в библиотеку collections
. Обязательно ознакомьтесь с официальной документацией после чтения этой главы.