Руководство по словарям Python

Оглавление

  1. Что такое словарь Python?
  2. Как создавать и ссылаться на словари Python
  3. Практические примеры использования
  4. Итерация данных в словарях
  5. Словари как вложенные структуры данных
  6. Дом Python для JSON
  7. Проблемы и альтернативы словарям Python
  8. Соображения по производительности

Что такое словарь Python?

Второй после списка в Python, словарь или "dict" - это место в памяти для хранения серии значений - также называемое коллекцией. Особенностью словаря является то, что ссылки на значения делаются не по порядку с использованием числового индекса. Скорее, в словаре, на значения ссылаются с помощью определяемого пользователем ключа , так же как слова в физическом словаре являются "ключами", связанными с "значением" их смысла. Этот ключ обычно представляет собой строку, но может быть любым типом данных.

my_dict = {'my_key' : 'my_value'}

Например, вместо того, чтобы ссылаться на первое значение в списке с помощью my_list[0], можно ссылаться на любой элемент словаря по его ключу:

>>> my_dict['my_key']
‘my_value’

Такие явные ссылки более разборчивы, чем индексные обозначения списков, и улучшают сопровождаемость и производительность кода в большинстве ситуаций.

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

Как создавать и ссылаться на словари Python

Существует несколько способов объявления словаря, в зависимости от ситуации. Самый простой - заключить ключи и значения в фигурные скобки, например, так:

my_dict = {'key1': 1, 'key2': 2}

Вы также можете передавать пары ключ-значение в конструктор ключевого слова dict, хотя это менее распространено:

my_dict = dict(key1 = 1, key2 = 2)

Присвоение значений при объявлении полезно при возврате словаря с динамическими значениями или как часть лямбды или понимания. И ключи, и значения могут быть ссылками на переменные, определенные в другом месте, что позволяет динамическое присвоение.

Иногда необходимо объявить пустой словарь, поскольку значения могут быть добавлены позже, но другим частям кода нужно на что-то сослаться в это время.

Чтобы объявить пустой словарь:

my_dict = {}
my_dict = dict()

Значения могут быть добавлены в этот словарь, когда они становятся доступными с помощью оператора присваивания:

my_dict['key'] = 123

>>> my_dict
{'key': 123}

Словари в Python хранятся и на них ссылаются, как на любую другую переменную. Более того, словари могут храниться внутри словарей, и часто так и происходит. В этом случае просто ссылайтесь на хранимый словарь, как на любое другое значение - по его ключу.

my_dict = {
    'my_nested_dict':
        {
            'a_key': 'a_value',
            'another_key': 'another_value',
        }
}

Вежливо использовать пробельные символы таким образом, чтобы четко обозначить вложенные слои, сохраняя при этом соответствие лучшим практикам Python. Конкретный формат может быть определен автоформаттером IDE или линтером перед развертыванием.

Теперь мы можем ссылаться на вложенный словарь по его ключу:

my_variable = my_dict['my_nested_dict']

Понимание словаря - Less is More

Более продвинутой техникой определения словаря является использование понимания словаря в Python. Как и в случае со списком, в случае со словарем создается словарь динамического размера в формате, более сжатом, чем приведенная выше нотация:

automatic_dictionary = {key: value for (key, value) in < some_iterable >}

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

Практические примеры использования

Допустим, нам нужно быстро смоделировать и сохранить некоторые данные без использования шаблонов класса или волосатых SQL-запросов. Например, нам нужно хранить некоторые данные о пользователях веб-сайта.

Класс User может выглядеть следующим образом...

class User(object):
    """  Stores info about Users """

    def __init__(self, name, email, address, password, url):
        self.name = name
        self.email = email
        ...

    def send_email(self):
        """ Send an email to our user"""
        pass

    def __repr__():
        """Logic to properly format data"""

bill = User('Bill', 'bill@gmail.com', '123 Acme Dr.', 'secret-password',
            'http://www.bill.com')
bill.send_email()

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

bill = {'email': 'bill@gmail.com',
    'address': '123 Acme Dr.',
    'password': 'secret-password',
    'url': 'http://www.bill.com'}

def send_email(user_dict):
    pass
    # smtp email logic …

send_email(bill['email'])  # bracket notation or …
send_email(bill.get('email'))  # .get() method is handy, too

Теперь мы можем иметь данные Bill так же интуитивно понятно, как и объект Bill, вместе с половиной кода.

Итерация данных, хранящихся в словарях

Поскольку ответы JSON часто представляют собой списки словарей (возможно, разобранный ответ API для создания списка экземпляров пользователей), мы можем итеративно просмотреть его, чтобы создать несколько экземпляров пользователей.

json_response = [{
  'id': 1,
  'first_name': 'Florentia',
  'last_name': 'Schell'",
  'email': 'fschelle0@nyu.edu',
  'url': 'https://wired.com'
}, {
  'id': 2,
  'first_name': 'Montague',
  'last_name': 'McAteer',
  'email': 'mmcateer1@zdnet.com',
  'url': 'https://domainmarket.com'
}, {
  'id': 3,
  'first_name': 'Dav',
  'last_name': 'Yurin',
  'email': 'dyurin2@e-recht24.de',
  'url': 'http://wufoo.com'
}]

Обратите внимание на естественную структуру словарей в виде рядов данных. Мы можем легко итерировать эти строки для создания наших объектов User.

users = []
for i in json_response:
    users.append(User(
        name=i['first_name'] + i['last_name'],
        email = i['email'],
        url=i['url'],
        # ...
    ))

Словари как вложенные структуры данных

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

Во-первых, значением в словаре может быть любой объект python, а коллекции объектов часто создаются с помощью значений из словаря. Значения связаны с другими значениями простым "прикреплением". То есть, помещая одно значение в список или словарь, с первым значением в качестве ключа. Хотя созданный таким образом словарь может показаться сложным, на самом деле извлечь конкретные значения из словаря гораздо проще, чем написать SQL-запрос.

Благодаря своей структуре, словари Python являются хорошим способом понимания других вложенных структур данных (таких как JSON или XML) - которые часто называют нереляционными, охватывающими все, кроме реляционных баз данных, таких как MySQL, PostgreSQL, а также других.

Преимущество менее жестких структур в том, что конкретные значения легко доступны. Недостатком является то, что наборы значений на соответствующем "уровне" вложенности под другими ключами сложнее связать друг с другом, и получающийся код более многословен. Если данные естественным образом разбиваются на столбцы и строки, то более подходящим будет что-то вроде Pandas DataFrame или Numpy ndarray, позволяющие ссылаться на значения по их относительному расположению в векторном пространстве.

Дом Python для JSON

Хотя между словарями Python и JSON (JavaScript Object Notation) есть некоторые тонкие различия, сходство между этими двумя структурами данных является основным бонусом для разработчиков, потребляющих данные из других источников. На самом деле, вызов метода .json() на ответ из библиотеки запросов вернет словарь.

В последнее время JSON стал де-факто средством обмена данными через API, при этом языки разметки, такие как XML и YAML, значительно отстают. Это лидерство, скорее всего, объясняется распространенностью JavaScript и необходимостью для веб-служб уметь "говорить" на JavaScript с другими веб-службами. По мнению некоторых, JSON просто меньше работы по распаковке.

К счастью или, возможно, по замыслу, Python хорошо подходит для потребления JSON через свою родную структуру данных: словарь Python. Тем не менее, вот некоторые различия:

  1. JSON - это сериализация: В то время как разработчики Python привыкли манипулировать объектами Python в памяти, JSON - это совсем другая история. Вместо этого, JSON - это стандарт для сериализации всех видов данных для отправки, как телеграммы по HTTP. Как только JSON добирается по проводам, его можно десериализовать или загрузить в объект Python.
  2. JSON может быть строкой: Перед тем, как объекты JSON попадают в логику Python, они представляют собой строки, обычно отправляемые в ответ на HTTP-запрос, а затем разбираемые различными способами. Ответы JSON обычно выглядят как списки словарей, окруженные кавычками. Удобно, что списки словарей могут быть легко разобраны в еще более полезные объекты, такие как Pandas DataFrames (Pandas - это мощный инструмент анализа данных для Python). При загрузке и сбросе (сериализации) объектов JSON, в какой-то момент они станут строками в Python.
  3. Дублирование ключей: Ключи словарей Python должны быть уникальными. Другими словами, some_dictionary.keys() будет представлять собой набор уникальных значений. Это не относится к JSON - что немного необычно, поскольку это, кажется, противоречит цели ключей в первую очередь - но никто никогда не говорил, что JSON является питоидным. Дубликаты ключей должны быть явно обработаны при преобразовании JSON в объект Python, иначе только одна пара ключ-значение пройдет через него.

Подводные камни и альтернативы, подобные словарю

Словари невероятно полезны, но некоторые аспекты спецификации языка заставляют словари вести себя неправильно. Например, при итерации по словарю разработчик может сослаться на пару ключ-значение, которая не была определена. Вместо того чтобы вернуть "None", словарь Python выдаст ошибку и распечатает трассировку, полностью прекратив выполнение, если ошибка не будет обработана. Такое поведение может замедлить цикл разработки.

>>> print(my_dict['my_key'])
Traceback (most recent call last):
  File '<input>', line 1, in <module>
KeyError: 'my_key'

Поскольку часто программе может потребоваться просто "проверить" существование пары ключ-значение без выброса ошибки, у разработчика есть и другие варианты. Первый - импортировать объект defaultdict из модуля коллекций, удобное переопределение, автоматически заполняемое значениями по умолчанию. Вместо того чтобы выдать ошибку, возвращается значение по умолчанию.

Во-вторых, метод .get() стандартного словаря может вернуть любое значение, переданное в качестве второго аргумента. Таким образом, вместо скобочной нотации ссылка на значение выглядит как ...

just_checking = my_dict.get('my_key’, None)
>>> print(just_checking)
None

Намного лучше!

OrderedDict

Словари определяются как "неупорядоченные" коллекции пар ключ-значение, что может быть неудобно. Чтобы добавить упорядоченное поведение, у нас есть OrderedDict, также из модуля коллекций. Как следует из названия, OrderedDict возвращает пары в том порядке, в котором они определены.

Хотя он не такой легкий, как стандартный словарь, многие разработчики предпочитают использовать OrderedDict, поскольку его поведение более предсказуемо. При итерации по стандартному словарю пары ключ-значение будут возвращаться в случайном порядке. OrderedDict всегда возвращает пары в одном и том же порядке, что может быть полезно при поиске определенных пар в большом наборе данных. Сторонники defaultdict и OrderedDict не спрашивают "Почему?" - они спрашивают "Почему нет?"

Соображения по производительности

Вы наблюдаете низкую производительность в своем приложении на Python? Прекратите итерации по спискам и начните ссылаться на значения в словаре.

Технически функцию словаря можно эмулировать с помощью списков. Создание пар ключ-значение с помощью списков часто является вводным упражнением в программировании. Однако для такого высокоуровневого языка, как Python, очень важно иметь высокопроизводительную реализацию. Одна из причин заключается в том, что словари используются внутри самой реализации языка Python

Другая причина заключается в том, что словари работают экспоненциально быстрее, чем списки. В списке Python, чтобы найти конкретный элемент, необходимо проверить каждый элемент, пока не будет найдено совпадение. В словаре проверяется только элемент (или объект, или коллекция), связанный с определенным ключом. Это значительно повышает производительность, часто на порядки.

Куда двигаться дальше...

Лучший способ познакомиться со словарями - это практика! Попробуйте выполнить итерации по словарям, сохраняя ключи и значения в отдельных списках, а затем переназначая их друг другу в правильном порядке.

Попробуйте создать интересные серии объектов из словарей, а словари из объектов. Если бы вам нужно было хранить 1 000 рядов данных в словаре, какой бы паттерн Python подошел для решения этой задачи?

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

https://django.fun/ru/docs/python/3.10/tutorial/datastructures/

Словарь Python - это фундаментальная структура данных в Python и основной компонент спецификации языка Python. При бережном обращении словари становятся высокопроизводительными инструментами для хранения и доступа к сложным данным в явном, читабельном и - самое главное - питоническом виде.

Вернуться на верх