Collections — это встроенный модуль Python, предоставляющий такие полезные типы данных, как контейнеры.
Контейнерные типы данных позволяют нам хранить и получать доступ к значениям удобным способом. Как правило, мы используем списки, кортежи и словари. Но при работе со структурированными данными нам нужны более умные объекты.
Сегодня мы разберем различные структуры данных, поддерживаемые модулем collections
, и на примерах рассмотрим, когда их стоит использовать.
Содержание
- Именованный кортеж namedtuple
- Что такое именованный кортеж
- Альтернативный способ создания namedtuple
- Зачем использовать namedtuple вместо обычного словаря
- Как создать namedtuple из словаря в Python
- Как заменить атрибут в именованном кортеже
- Counter
- Defaultdict
- OrderedDict
- Что происходит, когда вы удаляете и повторно вставляете ключи в OrderedDict
- Сортировка с помощью OrderedDict
- ChainMap
- Что происходит, когда у нас есть избыточные ключи в ChainMap
- Как добавить новый словарь в ChainMap
- Как изменить порядок словарей в ChainMap
- UserList
- UserString
- UserDict
Итак, давайте приступать! Однако сначала нам обязательно нужно импортировать collections
:
# Import the collections module import collections
Именованный кортеж namedtuple
Что такое именованный кортеж?
namedtuple
можно представить как расширенную версию обычного кортежа tuple
либо как быстрый способ создать класс с определенными именованными атрибутами.
Ключевое различие между кортежем и именованным кортежем заключается в следующем. Кортеж позволяет вам обращаться к данным через индексы, а с именованным кортежем вы можете получить доступ к элементам непосредственно по их именам.
Фактически вы можете определить, какие атрибуты может содержать именованный кортеж, и создать несколько его экземпляров. Точно так же, как вы поступили бы с классами.
Таким образом, с точки зрения функциональности он больше похож на класс, хотя и называется кортежем.
[python_ad_block]Давайте создадим namedtuple
, который представляет из себя фильм movie
с такими атрибутами, как жанр (genre
), рейтинг (rating
) и ведущий актер (lead_actor
).
# Creating a namedtuple. # The field values are passed as a string seperated by ' ' from collections import namedtuple movie = namedtuple('movie','genre rating lead_actor') # Create instances of movie ironman = movie(genre='action',rating=8.5,lead_actor='robert downey junior') titanic = movie(genre='romance',rating=8,lead_actor='leonardo dicaprio') seven = movie(genre='crime',rating=9,lead_actor='Brad Pitt')
Теперь мы можем получить доступ к любой информации о нашем фильме, используя идентификатор. Это довольно быстро и удобно.
# Access the fields print(titanic.genre) print(seven.lead_actor) print(ironman.rating) # Вывод: # romance # Brad Pitt # 8.5
Альтернативный способ создания namedtuple
В качестве альтернативы вы можете передать список, состоящий из имен полей, вместо просто имен полей, разделенных пробелом.
К примеру, это может выглядеть так:
# Creating namedtuple by passing fieldnames as a list of strings book = namedtuple('book',['price','no_of_pages','author']) harry_potter = book('500','367','JK ROWLING') pride_and_prejudice = book('300','200','jane_austen') tale = book('199','250','christie') print('Price of pride and prejudice is ',pride_and_prejudice.price) print('author of harry potter is',harry_potter.author) # Вывод: # Price of pride and prejudice is 300 # author of harry potter is JK ROWLING
Доступ к элементам в namedtuple
возможен как по индексу, так и по идентификатору.
print(tale[1]) # Вывод: # 250
Зачем использовать namedtuple вместо обычного словаря
Основным преимуществом namedtuple
является то, что он занимает меньше места (памяти), чем аналогичный словарь.
Поэтому, в случае больших данных именованные кортежи эффективны.
Пример:
# Create a dict and namedtuple with same data and compare the size import random import sys # Create Dict dicts = {'numbers_1': random.randint(0, 10000),'numbers_2':random.randint(5000,10000)} print('Size or space occupied by dictionary',sys.getsizeof(dicts)) # converting same dictionary to a namedtuple data=namedtuple('data',['numbers_1','numbers_2']) my_namedtuple= data(**dicts) print('Size or space occupied by namedtuple',sys.getsizeof(my_namedtuple)) # Вывод: # Size or space occupied by dictionary 240 # Size or space occupied by namedtuple 64
Выполняя приведенный выше код, вы обнаружите, что namedtuple
имеет размер 64 байта, тогда как словарь занимает гораздо больше — 240 байт. Это почти в 4 раза больше памяти.
А теперь представьте себе эффект при обработке большого количества таких объектов. В общем, с целью экономии памяти гораздо лучше использовать именованные кортежи.
Как создать namedtuple из словаря в Python
Вы заметили, как мы преобразовали словарь в именованный кортеж с помощью оператора **
?
Все, что вам нужно сделать для этого — определить структуру namedtuple
и передать словарь (**dict
) этому именованному кортежу в качестве аргумента. Единственное требование заключается в том, что ключи словаря должны совпадать с именами полей namedtuple
.
# Convert a dictionary into a namedtuple dictionary=dict({'price':567,'no_of_pages':878,'author': 'cathy thomas'}) # Convert book = namedtuple('book',['price','no_of_pages','author']) print(book(**dictionary)) # Вывод: # book(price=567, no_of_pages=878, author='cathy thomas')
Как заменить атрибут в именованном кортеже
Что делать, если значение одного атрибута необходимо изменить?
Вам нужно обновить его в данных. Для этого просто воспользуемся методом ._replace()
:
# update the price of the book my_book=book('250','500','abc') my_book._replace(price=300) print("Book Price:", my_book.price) # Вывод: # Book Price: 250
Counter
Объект counter предоставляется библиотекой collections
. Давайте поподробнее разберем, что он собой представляет.
К примеру, у вас есть список каких-то случайных чисел. Что, если вы хотите узнать, сколько раз встречается каждое число?
Счетчик counter
позволяет легко вычислить частоту. Он работает не только с числами, но и с любым итерируемыми объектами, такими как строки и списки.
Counter
— это подкласс dict
, используемый для подсчета хешируемых объектов.
Он возвращает словарь с элементами в качестве ключей и “счетчиком” (количество вхождений элемента) в качестве значений.
Примеры
Теперь давайте разберем некоторые примеры.
#importing Counter from collections from collections import Counter numbers = [4,5,5,2,22,2,2,1,8,9,7,7] num_counter = Counter(numbers) print(num_counter) # Вывод: # Counter({2: 3, 5: 2, 7: 2, 4: 1, 22: 1, 1: 1, 8: 1, 9: 1})
Давайте используем счетчик counter
, чтобы найти частоту вхождений каждого символа в строке.
#counter with strings string = 'lalalalandismagic' string_count = Counter(string) print(string_count) # Вывод: # Counter({'a': 5, 'l': 4, 'i': 2, 'n': 1, 'd': 1, 's': 1, 'm': 1, 'g': 1, 'c': 1})
Как мы видим, counter позволяет посмотреть, какие элементы есть в строке и сколько их.
Усложним задачу. Допустим, у вас есть предложение и вы хотите посмотреть количество слов в нем. Как это сделать?
При помощи функции split()
можно составить список слов в предложении и передать его в Counter()
.
# Using counter on sentences line = 'he told her that her presentation was not that good' list_of_words = line.split() line_count=Counter(list_of_words) print(line_count) # Вывод: # Counter({'her': 2, 'that': 2, 'he': 1, 'told': 1, 'presentation': 1, 'was': 1, 'not': 1, 'good': 1})
Как найти наиболее частотные элементы с помощью счетчика
Счетчик очень полезен в реальных приложениях. Особенно, когда вам нужно обработать большие данные, и вы хотите узнать частотность некоторых элементов. Давайте рассмотрим несколько очень полезных методов, использующих counter
.
Counter().most_common([n])
Такое выражение возвращает список «n
наиболее распространенных элементов» вместе с их количеством в порядке убывания.
# Passing different values of n to most_common() function print('The 2 most common elements in `numbers` are', Counter(numbers).most_common(2)) print('The 3 most common elements in `string` are', Counter(string).most_common(3)) # Вывод: # The 2 most common elements in `numbers` are [(2, 3), (5, 2)] # The 3 most common elements in `string` are [('a', 5), ('l', 4), ('i', 2)]
Метод most_common()
можно использовать для вывода наиболее часто повторяющегося элемента. Используется в частотном анализе.
print(Counter(list_of_words).most_common(1)) # Вывод: # [('her', 2)]
Этот же метод можно использовать для поиска наиболее частотного символа в строке.
print(Counter(string).most_common(3)) # Вывод: # [('a', 5), ('l', 4), ('i', 2)]
Что произойдет, если вы не укажете n
при использовании most_common(n)
?
Тогда все элементы с их количеством вхождений будут напечатаны в порядке убывания количества.
print(Counter(string).most_common()) # Вывод: # [('a', 5),('l', 4),('i', 2),('n', 1),('d', 1),('s', 1),('m', 1),('g', 1),('c', 1)]
Еще есть метод Counter().elements()
, который возвращает все элементы, количество которых больше 0.
print(sorted(string_count.elements())) # Вывод: # ['a', 'a', 'a', 'a', 'a', 'c', 'd', 'g', 'i', 'i', 'l', 'l', 'l', 'l', 'm', 'n', 's']
Defaultdict
Словарь представляет из себя неупорядоченный набор ключей и значений.
В парах ключ:значение
ключи должны быть уникальны и неизменяемы. Поэтому список не может быть ключом словаря, так как он изменяемый. А вот кортеж может.
# Dict with tuple as keys: OKAY {('key1', 'key2'): "value"} # Dict with list as keys: ERROR {['key1', 'key2']: "value"}
Чем defaultdict отличается от простого словаря?
Если вы попытаетесь получить доступ к ключу, которого нет в словаре, он выдаст ошибку KeyError
. В то время как при использовании defaultdict
такой ошибки не будет.
Если вы попробуете обратиться к отсутствующему ключу, defaultdict
просто вернет значение по умолчанию.
Синтаксис будет следующим: defaultdict(default_factory)
.
При обращении к отсутствующему ключу функция default_factory
вернет значение по умолчанию.
# Creating a defaultdict and trying to access a key that is not present. from collections import defaultdict def_dict = defaultdict(object) def_dict['fruit'] = 'orange' def_dict['drink'] = 'pepsi' print(def_dict['chocolate']) # Вывод: # <object object at 0x7fc14cc4e1f0>
При запуске этого кода вы не получите KeyError
.
Если вы хотите вывести сообщение о том, что значение запрошенного ключа отсутствует, можно определить собственную функцию и передать ее в defaultdict
.
К примеру, это может выглядеть так:
# Passing a function to return default value def print_default(): return 'value absent' def_dict=defaultdict(print_default) print(def_dict['chocolate']) # Вывод: # value absent
Во всем остальном defaultdict ничем не отличается от обычного словаря. Синтаксис команд полностью идентичен.
Вы также можете обойти ошибку KeyError
и при использовании обычного словаря — с помощью метода get()
.
# Make dict return a default value mydict = {'a': 'Apple', 'b': 'Ball'} print(mydict.get('c', 'NOT PRESENT')) # Вывод: # NOT PRESENT
OrderedDict
Словарь — это НЕупорядоченная коллекция пар ключ-значение. Однако OrderedDict
поддерживает упорядочивание ключей.
Это в некотором роде подкласс словаря dict
.
Давайте создадим обычный словарь и сделаем его OrderedDict
, чтобы показать, в чем заключается разница.
# create a dict and print items vehicle = {'bicycle': 'hercules', 'car': 'Maruti', 'bike': ' Harley', 'scooter': 'bajaj'} print('This is normal dict') for key,value in vehicle.items(): print(key,value) print('-------------------------------') # Create an OrderedDict and print items from collections import OrderedDict ordered_vehicle=OrderedDict() ordered_vehicle['bicycle']='hercules' ordered_vehicle['car']='Maruti' ordered_vehicle['bike']='Harley' print('This is an ordered dict') for key,value in ordered_vehicle.items(): print(key,value) # Вывод: # This is normal dict # bicycle hercules # car Maruti # bike Harley # scooter bajaj ------------------------------- # This is an ordered dict # bicycle hercules # car Maruti # bike Harley
В OrderedDict
даже после изменения значения некоторых ключей порядок остается прежним.
# I have changed the value of car in this ordered dictionary. ordered_vehicle['car']='BMW'# I have changed the value of car in this ordered dictionary. for key,value in ordered_vehicle.items(): print(key,value) # Вывод: # bicycle hercules # car BMW # bike harley davison
Что происходит, когда вы удаляете и повторно вставляете ключи в OrderedDict
При удалении ключа информация о его порядке также удаляется. Когда вы повторно вставляете ключ, он обрабатывается как новая запись и соответствующая информация сохраняется.
# deleting a key from an OrderedDict ordered_vehicle.pop('bicycle') for key,value in ordered_vehicle.items(): print(key,value) # Вывод: # car BMW # bike harley davison
При повторной вставке ключа запись считается новой.
# Reinserting the same key and print ordered_vehicle['bicycle']='hercules' for key,value in ordered_vehicle.items(): print(key,value) # Вывод: # car BMW # bike harley davison # bicycle hercules
Вы видите, что bicycle
находится в конце, так как порядок изменился, когда мы удалили ключ.
Есть несколько полезных команд, которые можно использовать с OrderedDict. К примеру, мы можем выполнять функции сортировки.
Сортировка с помощью OrderedDict
Сортировка элементов, например, по возрастанию значений, может помочь в анализе данных. Давайте посмотрим, что мы можем сделать.
- Сортировка элементов по ключу
KEY
(в порядке возрастания):
# Sorting items in ascending order of their keys drinks = {'coke':5,'apple juice':2,'pepsi':10} OrderedDict(sorted(drinks.items(), key=lambda t: t[0])) # Вывод: # OrderedDict([('apple juice', 2), ('coke', 5), ('pepsi', 10)])
- Сортировка пар по значению
VALUE
(в порядке возрастания):
# Sorting according to values OrderedDict(sorted(drinks.items(), key=lambda t: t[1])) # Вывод: # OrderedDict([('apple juice', 2), ('coke', 5), ('pepsi', 10)])
- Сортировка словаря по длине строки ключа (в порядке возрастания):
# Sorting according to length of key string OrderedDict(sorted(drinks.items(), key=lambda t: len(t[0]))) # Вывод: # OrderedDict([('coke', 5), ('pepsi', 10), ('apple juice', 2)])
ChainMap
ChainMap
— это контейнерный тип данных, в котором хранится несколько словарей.
Если у вас несколько связанных или похожих словарей, зачастую их можно хранить вместе, в ChainMap
.
Распечатать все элементы ChainMap
можно при помощи .map
:
# Creating a ChainMap from 3 dictionaries. from collections import ChainMap dic1={'red':5,'black':1,'white':2} dic2={'chennai':'tamil','delhi':'hindi'} dic3={'firstname':'bob','lastname':'mathews'} my_chain = ChainMap(dic1,dic2,dic3) my_chain.maps # Вывод: # [{'black': 1, 'red': 5, 'white': 2}, {'chennai': 'tamil', 'delhi': 'hindi'},{'firstname': 'bob', 'lastname': 'mathews'}]
Также мы можем вывести ключи всех словарей, используя функцию .keys()
:
print(list(my_chain.keys())) # Вывод: # ['firstname', 'lastname', 'chennai', 'delhi', 'red', 'black', 'white']
Можно получить и значения всех словарей в ChainMap
— при помощи функции .values()
.
print(list(my_chain.values())) # Вывод: # ['bob', 'mathews', 'tamil', 'hindi', 5, 1, 2]
Что происходит, когда у нас есть избыточные ключи в ChainMap?
Возможно, что 2 словаря содержат один и тот же ключ. К примеру, как это показано ниже:
# Creating a chainmap whose dictionaries do not have unique keys dic1 = {'red':1,'white':4} dic2 = {'red':9,'black':8} chain = ChainMap(dic1,dic2) print(list(chain.keys())) # Вывод: # ['black', 'red', 'white']
Обратите внимание, что red
не повторяется, он печатается только один раз.
Как добавить новый словарь в ChainMap?
Вы можете добавить новый словарь в начало ChainMap
, используя метод .new_child()
.
# Add a new dictionary to the chainmap through .new_child() print('original chainmap', chain) new_dic={'blue':10,'yellow':12} chain=chain.new_child(new_dic) print('chainmap after adding new dictioanry',chain) # Вывод: # original chainmap ChainMap({'red': 1, 'white': 4}, {'red': 9, 'black': 8}) # chainmap after adding new dictioanry ChainMap({'blue': 10, 'yellow': 12}, {'red': 1, 'white': 4}, {'red': 9, 'black': 8})
Как изменить порядок словарей в ChainMap?
Порядок, в котором словари хранятся в ChainMap
, можно изменить с помощью функции reversed()
.
# We are reversing the order of dictionaries using reversed() function print('orginal chainmap', chain) chain.maps = reversed(chain.maps) print('reversed Chainmap', str(chain)) # Вывод: # orginal chainmap ChainMap({'blue': 10, 'yellow': 12}, {'red': 1, 'white': 4}, {'red': 9, 'black': 8}) # reversed Chainmap ChainMap({'red': 9, 'black': 8}, {'red': 1, 'white': 4}, {'blue': 10, 'yellow': 12})
UserList
Надеюсь, вы знакомы со списками в Python. Если нет, то почитать о них подробнее можно в статье «Списки в Python: полное руководство для начинающих».
UserList
— это похожий на список контейнерный тип данных, который является классом-оболочкой для списков.
Синтаксис будет следующим: collections.UserList([list])
.
Вы передаете обычный список в качестве аргумента userlist
. Этот список хранится в атрибуте ‘data’ и доступен через метод UserList.data
.
# Creating a user list with argument my_list from collections import UserList my_list=[11,22,33,44] # Accessing it through `data` attribute user_list=UserList(my_list) print(user_list.data) # Вывод: # [11, 22, 33, 44]
В чем польза UserLists?
Предположим, вы хотите удвоить все элементы в определенных списках. Или, может быть, вы хотите убедиться, что ни один элемент не может быть удален из заданного списка.
В таких случаях нам нужно добавить в наши списки определенное «поведение», что можно сделать с помощью UserLists
.
Давайте рассмотрим, как можно использовать UserList
для переопределения функциональности встроенного метода. Приведенный ниже код предотвращает добавление нового значения в список:
# Creating a userlist where adding new elements is not allowed. class user_list(UserList): # function to raise error while insertion def append(self,s=None): raise RuntimeError("Authority denied for new insertion") my_list=user_list([11,22,33,44]) # trying to insert new element my_list.append(55)
#> --------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) <ipython-input-2-e8f22159f6e0> in <module> 4 5 my_list=user_list([11,22,33,44]) ----> 6 my_list.append(55) 7 print(my_list) <ipython-input-2-e8f22159f6e0> in append(self, s) 1 class user_list(UserList): 2 def append(self,s=None): ----> 3 raise RuntimeError("Authority denied for new insertion") 4 5 my_list=user_list([11,22,33,44]) RuntimeError: Authority denied for new insertion
Код выводит сообщение RunTimeError
и не позволяет добавить элемент в список. Это может быть полезно, если вы хотите не допустить внесения информации после определенного срока.
UserString
Подобно тому, как UserLists
является классом-оболочкой для списков, UserString
является классом-оболочкой для строк.
UserString
позволяет добавлять к строке определенное поведение. Вы можете передать этому классу любой конвертируемый в строку аргумент и затем получить доступ к этой строке, используя атрибут data.
# import Userstring from collections import UserString num=765 # passing an string convertible argument to userdict user_string = UserString(num) # accessing the string stored user_string.data # Вывод: # '765'
Как видите, число 765
было преобразовано в строку «765»
, и доступ к ней можно получить с помощью метода UserString.data
.
Как и когда можно использовать UserString
UserString
можно использовать для изменения строк или выполнения определенных функций.
Предположим, вы хотите удалить определенное слово из текстового файла (где бы оно ни было). Возможно, некоторые слова в тексте неуместны.
Давайте посмотрим на пример того, как UserString
можно использовать для удаления определенных слов из строки.
# Using UserString to remove odd words from the textfile class user_string(UserString): def append(self, new): self.data = self.data + new def remove(self, s): self.data = self.data.replace(s, "") text='apple orange grapes bananas pencil strawberry watermelon eraser' fruits = user_string(text) for word in ['pencil','eraser']: fruits.remove(word) print(fruits) # Вывод: # apple orange grapes bananas strawberry watermelon
В этом примере pencil
и eraser
были удалены с помощью функционального класса user_string
.
Рассмотрим другой случай. Что делать, если вам нужно заменить слово другим во всем файле?
При помощи UserString
сделать это просто. Мы определили функцию внутри класса, чтобы заменить определенное слово на The Chairman
во всем тексте.
# using UserString to replace the name or a word throughout. class user_string(UserString): def append(self, new): self.data = self.data + new def replace(self,replace_text): self.data = self.data.replace(replace_text,'The Chairman') text = 'Rajesh concluded the meeting very late. Employees were disappointed with Rajesh' document = user_string(text) document.replace('Rajesh') print(document.data) # Вывод: # The Chairman concluded the meeting very late. Employees were disappointed with The Chairman
Как видите, слово Rajesh
везде заменено на The Chairman
.
UserDict
Это класс-оболочка для словарей. Его синтаксис аналогичен UserList
и UserString
.
Мы передаем словарь в качестве аргумента, который хранится в атрибуте ‘data’.
# importing UserDict from collections import UserDict my_dict={'red':'5','white':2,'black':1} # Creating an UserDict user_dict = UserDict(my_dict) print(user_dict.data) # Вывод: # {'red': '5', 'white': 2, 'black': 1}
Как можно использовать UserDict
UserDict
позволяет нам создать словарь и модифицировать его под наши нужды. Давайте посмотрим на пример того, как UserDict
можно использовать для переопределения функциональности встроенного метода. Приведенный ниже код предотвращает удаление пары ключ-значение.
# Creating a Dictionary where deletion of an is not allowed class user_dict(UserDict): # Function to stop delete/pop def pop(self, s = None): raise RuntimeError("Not Authorised to delete") data = user_dict({'red':'5','white':2,'black':1}) # try to delete a item data.pop(1)
#> --------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) <ipython-input-16-2e576a68d2ad> in <module> 12 13 #try to delete a item ---> 14 data.pop(1) <ipython-input-16-2e576a68d2ad> in pop(self, s) 5 def pop(self, s = None): 6 ----> 7 raise RuntimeError("Not Authorised to delete") 8 9 RuntimeError: Not Authorised to delete
Вы получите сообщение RunTimeError
. Это может помочь, если вы не хотите потерять данные.
Что делать, если некоторые ключи имеют ненужные значения, и вам нужно заменить их на null
или 0? Эту проблему можно решить следующим образом:
class user_dict(UserDict): def replace(self,key): self[key]='0' file= user_dict({'red':'5','white':2,'black':1,'blue':4567890}) # Delete 'blue' and 'yellow' for i in ['blue','yellow']: file.replace(i) print(file) # Вывод: # {'red': '5', 'white': 2, 'black': 1, 'blue': '0', 'yellow': '0'}
Поле с ненужными значениями заменено на 0. И это лишь простые примеры того, как UserDict
позволяет создать словарь с необходимой функциональностью.
Заключение
Итак, мы разобрали основные типы контейнерных данных с примерами. Они значительно повышают эффективность работы при использовании на больших наборах данных.
Надеемся, данная статья была вам полезна! Успехов в написании кода!
Перевод статьи «Python Collections – An Introductory Guide».