- en
- Language: ru
- Documentation version: latest
12. Коллекции¶
Python поставляется с модулем, который содержит ряд контейнерных типов данных, называемых Collections. Мы поговорим о некоторых из них и обсудим их полезность.
О них мы и поговорим:
defaultdict
OrderedDict
Counter
deque
namedtuple
enum.Enum
(вне модуля; Python 3.4+)
12.1. defaultdict
¶
Я лично часто использую defaultdict. В отличие от dict
, в defaultdict
вам не нужно проверять, присутствует ли ключ или нет. Поэтому мы можем сделать следующее:
from collections import defaultdict
colours = (
('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)
# output
# defaultdict(<type 'list'>,
# {'Arham': ['Green'],
# 'Yasoob': ['Yellow', 'Red'],
# 'Ahmed': ['Silver'],
# 'Ali': ['Blue', 'Black']
# })
Еще один очень важный случай использования - добавление во вложенные списки внутри словаря. Если key
еще не присутствует в словаре, то вас встретит KeyError
. defaultdict
позволяет нам обойти эту проблему хитрым способом. Сначала я приведу пример с использованием dict
, который вызывает KeyError
, а затем расскажу о решении с использованием defaultdict
.
Проблема:
some_dict = {}
some_dict['colours']['favourite'] = "yellow"
# Raises KeyError: 'colours'
Решение:
from collections import defaultdict
tree = lambda: defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"
# Works fine
Вы можете вывести some_dict
, используя json.dumps
. Вот пример кода:
import json
print(json.dumps(some_dict))
# Output: {"colours": {"favourite": "yellow"}}
12.2. OrderedDict
¶
OrderedDict
сохраняет свои записи отсортированными в том виде, в котором они были первоначально вставлены. Перезапись значения существующего ключа не изменяет его положение. Однако удаление и повторная вставка записи перемещает ключ в конец словаря.
Проблема:
colours = {"Red" : 198, "Green" : 170, "Blue" : 160}
for key, value in colours.items():
print(key, value)
# Output:
# Green 170
# Blue 160
# Red 198
# Entries are retrieved in an unpredictable order
Решение:
from collections import OrderedDict
colours = OrderedDict([("Red", 198), ("Green", 170), ("Blue", 160)])
for key, value in colours.items():
print(key, value)
# Output:
# Red 198
# Green 170
# Blue 160
# Insertion order is preserved
12.3. Counter
¶
Счетчик позволяет подсчитывать количество повторений определенного элемента. Например, его можно использовать для подсчета количества отдельных любимых цветов:
from collections import Counter
colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)
favs = Counter(name for name, colour in colours)
print(favs)
# Output: Counter({
# 'Yasoob': 2,
# 'Ali': 2,
# 'Arham': 1,
# 'Ahmed': 1
# })
С его помощью можно также подсчитать наиболее часто встречающиеся строки в файле. Например:
with open('filename', 'rb') as f:
line_count = Counter(f)
print(line_count)
12.4. deque
¶
deque
предоставляет вам очередь с двойным концом, что означает, что вы можете добавлять и удалять элементы с любой стороны очереди. Прежде всего, необходимо импортировать модуль deque из библиотеки коллекций:
from collections import deque
Теперь мы можем создать объект deque.
d = deque()
Он работает как списки python и предоставляет вам несколько похожих методов. Например, вы можете сделать следующее:
d = deque()
d.append('1')
d.append('2')
d.append('3')
print(len(d))
# Output: 3
print(d[0])
# Output: '1'
print(d[-1])
# Output: '3'
Вы можете извлекать значения с обеих сторон deque:
d = deque(range(5))
print(len(d))
# Output: 5
d.popleft()
# Output: 0
d.pop()
# Output: 4
print(d)
# Output: deque([1, 2, 3])
Мы также можем ограничить количество элементов, которые может хранить deque. Для этого, когда мы достигнем максимального предела нашего deque, он будет просто вытаскивать элементы с противоположного конца. Лучше объяснить это на примере, так что вот, пожалуйста:
d = deque([0, 1, 2, 3, 5], maxlen=5)
print(d)
# Output: deque([0, 1, 2, 3, 5], maxlen=5)
d.extend([6])
print(d)
#Output: deque([1, 2, 3, 5, 6], maxlen=5)
Теперь всякий раз, когда вы вставляете значения после 5, крайнее левое значение будет выпадать из списка. Вы также можете расширять список в любом направлении, добавляя новые значения:
d = deque([1,2,3,4,5])
d.extendleft([0])
d.extend([6,7,8])
print(d)
# Output: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])
12.5. namedtuple
¶
Возможно, вы уже знакомы с кортежами. Кортеж - это неизменяемый список, который позволяет хранить последовательность значений, разделенных запятыми. Они похожи на списки, но имеют несколько ключевых отличий. Главное из них заключается в том, что в отличие от списков, вы не можете переназначить элемент в кортеже. Для доступа к значению в кортеже используются целочисленные индексы, такие как:
man = ('Ali', 30)
print(man[0])
# Output: Ali
Итак, что же такое namedtuples
? Они превращают кортежи в удобные контейнеры для простых задач. С именованными кортежами вам не нужно использовать целочисленные индексы для доступа к членам кортежа. Именованные кортежи можно рассматривать как словари, но в отличие от словарей они неизменяемы.
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
print(perry)
# Output: Animal(name='perry', age=31, type='cat')
print(perry.name)
# Output: 'perry'
Теперь вы видите, что мы можем получить доступ к членам кортежа просто по их имени с помощью .
. Давайте разберем это немного подробнее. Именованный кортеж имеет два обязательных аргумента. Это имя кортежа и поле_имени кортежа. В приведенном выше примере имя нашего кортежа было „Animal“, а поле_имени кортежа было „name“, „age“ и „type“. Namedtuple делает ваши кортежи самодокументирующимися. Вы можете легко понять, что происходит, бросив беглый взгляд на свой код. А поскольку вы не обязаны использовать целочисленные индексы для доступа к членам кортежа, это упрощает сопровождение вашего кода. Более того, поскольку экземпляры `namedtuple` не имеют словарей для каждого экземпляра, они легковесны и требуют не больше памяти, чем обычные кортежи. Это делает их быстрее, чем словари. Однако помните, что, как и в случае с кортежами, атрибуты в namedtuples неизменяемы. Это означает, что данный метод не будет работать:
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
perry.age = 42
# Output: Traceback (most recent call last):
# File "", line 1, in
# AttributeError: can't set attribute
Чтобы сделать свой код самодокументируемым, следует использовать именованные кортежи. Они обратно совместимы с обычными кортежами. Это означает, что вы можете использовать целочисленные индексы и с именованными кортежами:
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
print(perry[0])
# Output: perry
И последнее, но не менее важное: вы можете преобразовать именованный кортеж в словарь. Например:
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type="cat")
print(perry._asdict())
# Output: OrderedDict([('name', 'Perry'), ('age', 31), ...
12.6. enum.Enum
(Python 3.4+)¶
Другой полезной коллекцией является объект enum. Он доступен в модуле enum
, в Python 3.4 и выше (также доступен как backport в PyPI под названием enum34
.) Перечисления (enumerated type) - это способ организации различных вещей.
Рассмотрим кортеж Animal namedtuple из предыдущего примера. В нем было поле type
. Проблема в том, что его тип был строкой. Это создает для нас некоторые проблемы. Что если пользователь введет Cat
, потому что удерживал клавишу Shift? Или CAT
? Или kitten
?
Перечисления могут помочь нам избежать этой проблемы, не используя строки. Рассмотрим этот пример:
from collections import namedtuple
from enum import Enum
class Species(Enum):
cat = 1
dog = 2
horse = 3
aardvark = 4
butterfly = 5
owl = 6
platypus = 7
dragon = 8
unicorn = 9
# The list goes on and on...
# But we don't really care about age, so we can use an alias.
kitten = 1
puppy = 2
Animal = 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.kitten)
# And now, some tests.
>>> charlie.type == tom.type
True
>>> charlie.type
<Species.cat: 1>
Это гораздо менее подвержено ошибкам. Мы должны быть конкретными и использовать только перечисление для наименования типов.
Существует три способа доступа к членам перечисления. Например, все три метода дадут вам значение для cat
:
Species(1)
Species['cat']
Species.cat
Это был просто быстрый просмотр модуля collections
. Убедитесь, что вы прочитали официальную документацию после прочтения этой статьи.