OrderedDict против dict на Python: Подходящий инструмент для работы
Оглавление
- Выбор между OrderedDict и dict
- Начало работы с Python'ом OrderedDict
- Изучение уникальных возможностей OrderedDict в Python
- Объединение и обновление словарей с помощью операторов
- Принимая во внимание производительность
- Выбор подходящего словаря для работы
- Построение очереди на основе словаря
- Заключение
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Использование OrderedDict в Python
Иногда вам нужен словарь Python , который запоминает порядок своих элементов. В прошлом у вас был только один инструмент для решения этой конкретной проблемы: Python's OrderedDict. Это подкласс словаря, специально разработанный для запоминания порядка элементов, который определяется порядком вставки ключей.
В Python 3.6 это изменилось. Встроенный класс dict теперь также упорядочивает свои элементы. Из-за этого многие в сообществе Python теперь задаются вопросом, полезен ли по-прежнему OrderedDict. Более пристальный взгляд на OrderedDict покажет, что этот класс по-прежнему предоставляет ценные возможности.
В этом руководстве вы узнаете, как:
- Создавайте и используйте
OrderedDictобъекты в вашем коде - Определите различия между
OrderedDictиdict - Понять плюсы и минусы использования
OrderedDictvsdict
Обладая этими знаниями, вы сможете выбрать класс словаря, который наилучшим образом соответствует вашим потребностям, если хотите сохранить порядок элементов.
В конце руководства вы увидите пример реализации очереди на основе словаря с использованием OrderedDict, что было бы сложнее, если бы вы использовали обычный объект dict.
Бесплатный бонус: Нажмите здесь, чтобы получить шпаргалку по Python и изучить основы Python 3, такие как работа с типами данных, словари, списки и функции Python.
Выбор между OrderedDict и dict
В течение многих лет словари Python были неупорядоченными структурами данных. Разработчики Python привыкли к этому факту и полагались на списков или других последовательностей, когда им нужно было привести свои данные в порядок. Со временем разработчики обнаружили необходимость в словаре нового типа, в котором элементы были бы упорядочены.
Еще в 2008 году в PEP 372 была представлена идея добавления нового класса словаря в collections. Его основная цель состояла в том, чтобы запомнить порядок расположения элементов в соответствии с порядком, в котором были вставлены ключи. Это было началом OrderedDict.
Разработчики Core Python хотели заполнить этот пробел и предоставить словарь, который мог бы сохранять порядок вставляемых ключей. Что, в свою очередь, позволило более просто реализовать конкретные алгоритмы, основанные на этом свойстве.
OrderedDict был добавлен в стандартную библиотеку в Python 3.1. Его API по сути такой же, как и в dict. Однако OrderedDict выполняет итерацию по ключам и значениям в том же порядке, в котором они были вставлены. Если новая запись перезаписывает существующую запись, порядок элементов остается неизменным. Если запись удалена и вставлена повторно, то она будет перемещена в конец словаря.
В Python 3.6 появилась новая реализация dict. Эта новая реализация представляет собой большой выигрыш с точки зрения использования памяти и эффективности итераций. Кроме того, новая реализация предоставляет новую и несколько неожиданную функцию: dict объекты теперь сохраняют свои элементы в том же порядке, в котором они были введены. Изначально эта функция считалась деталью реализации, и в документации не рекомендовалось полагаться на нее.
Примечание: В этом руководстве вы сосредоточитесь на реализациях dict и OrderedDict, которые предоставляет CPython.
По словам Раймонда Хеттингера, разработчика core Python и соавтора OrderedDict, класс был специально разработан для упорядочивания элементов, в то время как новая реализация dict был разработан таким образом, чтобы быть компактным и обеспечивать быструю итерацию:
Текущий регулярный словарь основан на проекте, который я предложил несколько лет назад. Основными целями этого проекта были компактность и ускорение итераций по плотным массивам ключей и значений. Поддержание порядка было скорее артефактом, чем целью дизайна. Дизайн может поддерживать порядок, но это не его специализация.
В отличие от этого, я придал
collections.OrderedDictдругой дизайн (позже закодированный на C Эриком Сноу). Основная цель состояла в том, чтобы обеспечить эффективное поддержание порядка даже при серьезных рабочих нагрузках, таких какlru_cache, которые часто изменяют порядок, не затрагивая лежащий в основеdict. ДизайнOrderedDictспециально разработан таким образом, чтобы возможности упорядочивания были приоритетными за счет дополнительных затрат памяти и постоянного увеличения времени вставки.Моя цель по-прежнему заключается в том, чтобы
collections.OrderedDictиметь другой дизайн и другие эксплуатационные характеристики, отличные от обычных диктовок. В нем есть некоторые специфичные для порядка методы, которых нет в обычных диктовках (например,move_to_end()иpopitem(), которые эффективно отображаются с обоих концов).OrderedDictдолжен быть хорош в этих операциях, потому что именно это отличает его от обычных диктовок. (Источник)
В Python 3.7 функция упорядочивания элементов dict объектов была объявлена официальной частью Спецификация языка Python. Таким образом, с этого момента разработчики могли полагаться на dict, когда им требовался словарь, в котором элементы упорядочивались бы.
На этом этапе возникает вопрос: нужен ли OrderedDict по-прежнему после этой новой реализации dict? Ответ зависит от вашего конкретного варианта использования, а также от того, насколько явным вы хотите видеть его в своем коде.
На момент написания статьи некоторые особенности OrderedDict все еще делали его ценным и отличным от обычного dict:
- Сигнализация о намерении: Если вы используете
OrderedDictвместоdict, то ваш код дает понять, что порядок элементов в словаре важен. Вы ясно даете понять, что ваш код нуждается в порядке элементов в базовом словаре или полагается на него. - Управление порядком элементов: Если вам нужно переставить элементы в словаре или изменить их порядок, вы можете использовать
.move_to_end(), а также расширенный вариант.popitem(). - Поведение при проверке на равенство: Если ваш код сравнивает словари на предмет равенства, и порядок элементов важен при этом сравнении, то
OrderedDict- правильный выбор.
Есть, по крайней мере, еще одна причина продолжать использовать OrderedDict в вашем коде: обратная совместимость. Использование обычных объектов dict для сохранения порядка элементов приведет к нарушению работы вашего кода в средах, использующих версии Python старше 3.6.
Трудно сказать, скоро ли dict полностью заменит OrderedDict. В настоящее время OrderedDict по-прежнему предлагает интересные и ценные функции, которые вы, возможно, захотите учесть при выборе инструмента для конкретной работы.
Начало работы с Python OrderedDict
В Python OrderedDict является подклассом dict, который сохраняет порядок, в котором пары ключ-значение, обычно известный как элементы, которые вставляются в словарь. При выполнении итерации по объекту OrderedDict элементы перемещаются в исходном порядке. Если вы обновляете значение существующего ключа, порядок остается неизменным. Если вы удалите элемент и вставите его повторно, то этот элемент будет добавлен в конец словаря.
Принадлежность к подклассу dict означает, что он наследует все методы, предоставляемые обычным словарем. OrderedDict также обладает дополнительными функциями, о которых вы узнаете из этого руководства. Однако в этом разделе вы познакомитесь с основами создания и использования OrderedDict объектов в вашем коде.
Создание OrderedDict Объектов
В отличие от dict, OrderedDict не является встроенным типом, поэтому первым шагом для создания OrderedDict объектов является импорт класса из collections. Существует несколько способов создания упорядоченных словарей. Большинство из них идентичны способу создания обычного объекта dict. Например, вы можете создать пустой объект OrderedDict, создав экземпляр класса без аргументов:
>>> from collections import OrderedDict >>> numbers = OrderedDict() >>> numbers["one"] = 1 >>> numbers["two"] = 2 >>> numbers["three"] = 3 >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3)])предварительно> кодовый блок>В этом случае вы сначала импортируете
OrderedDictизcollections. Затем вы создаете пустой упорядоченный словарь, создавая экземплярOrderedDictбез предоставления аргументов конструктору.Вы можете добавить пары ключ-значение в словарь, указав ключ в квадратных скобках (
[]) и присвоив этому ключу значение. Когда вы ссылаетесь наnumbers, вы получаете итерацию пар ключ-значение, которая содержит элементы в том же порядке, в каком они были вставлены в словарь.Вы также можете передать итерацию элементов в качестве аргумента конструктору
OrderedDict:>>> from collections import OrderedDict >>> numbers = OrderedDict([("one", 1), ("two", 2), ("three", 3)]) >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3)]) >>> letters = OrderedDict({("a", 1), ("b", 2), ("c", 3)}) >>> letters OrderedDict([('c', 3), ('a', 1), ('b', 2)])предварительно> кодовый блок>При использовании последовательности , такой как
listилиtuple, порядок элементов в результирующем упорядоченном словаре соответствует исходному порядок расположения элементов во входной последовательности. Если вы используетеset, как во втором примере выше, то окончательный порядок элементов неизвестен до тех пор, пока не будет созданOrderedDict.Если вы используете обычный словарь в качестве инициализатора объекта
OrderedDictи используете Python 3.6 или более поздней версии, то вы получите следующее поведение:Python 3.9.0 (default, Oct 5 2020, 17:52:02) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from collections import OrderedDict >>> numbers = OrderedDict({"one": 1, "two": 2, "three": 3}) >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3)])предварительно> кодовый блок>Порядок элементов в объекте
OrderedDictсоответствует порядку в исходном словаре. С другой стороны, если вы используете версию Python ниже 3.6, то порядок элементов неизвестен:Python 3.5.10 (default, Jan 25 2021, 13:22:52) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from collections import OrderedDict >>> numbers = OrderedDict({"one": 1, "two": 2, "three": 3}) >>> numbers OrderedDict([('one', 1), ('three', 3), ('two', 2)])предварительно> кодовый блок>Поскольку словари в Python 3.5 не запоминают порядок своих элементов, вы не узнаете порядок в результирующем упорядоченном словаре до тех пор, пока объект не будет создан. С этого момента порядок сохраняется.
Вы можете создать упорядоченный словарь, передав аргументы ключевого слова в конструктор класса:
>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3)])предварительно> кодовый блок>Начиная с Python 3.6, функции сохраняют порядок аргументов ключевых слов, передаваемых при вызове. Таким образом, порядок элементов в приведенном выше
OrderedDictсоответствует порядку, в котором вы передаете аргументы ключевого слова конструктору. В более ранних версиях Python этот порядок неизвестен.Наконец,
OrderedDictтакже предоставляет.fromkeys(), который создает новый словарь из повторяемого набора ключей и присваивает всем его значениям общее значение:>>> from collections import OrderedDict >>> keys = ["one", "two", "three"] >>> OrderedDict.fromkeys(keys, 0) OrderedDict([('one', 0), ('two', 0), ('three', 0)])предварительно> кодовый блок>В этом случае вы создаете упорядоченный словарь, используя список ключей в качестве отправной точки. Второй аргумент
.fromkeys()предоставляет единое значение для всех элементов в словаре.Управление элементами в
OrderedDictПоскольку
OrderedDictявляется изменяемой структурой данных, вы можете выполнять операции изменения над ее экземплярами. Вы можете вставлять новые элементы, обновлять и удалять существующие элементы и т.д. Если вы вставляете новый элемент в существующий упорядоченный словарь, то этот элемент добавляется в конец словаря:>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3)]) >>> numbers["four"] = 4 >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3), ('four', 4)])предварительно> кодовый блок>Вновь добавленный элемент,
('four', 4), помещается в конец базового словаря, поэтому порядок существующих элементов остается неизменным, и словарь сохраняет порядок вставки.Если вы удаляете элемент из существующего упорядоченного словаря и вставляете этот же элемент снова, то новый экземпляр элемента помещается в конец словаря:
>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> del numbers["one"] >>> numbers OrderedDict([('two', 2), ('three', 3)]) >>> numbers["one"] = 1 >>> numbers OrderedDict([('two', 2), ('three', 3), ('one', 1)])предварительно> кодовый блок>Если вы удалите элемент
('one', 1)и вставите новый экземпляр того же элемента, то новый элемент будет добавлен в конец базового словаря.Если вы переназначаете или обновляете значение существующей пары ключ-значение в объекте
OrderedDict, то ключ сохраняет свою позицию, но получает новое значение:>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> numbers["one"] = 1.0 >>> numbers OrderedDict([('one', 1.0), ('two', 2), ('three', 3)]) >>> numbers.update(two=2.0) >>> numbers OrderedDict([('one', 1.0), ('two', 2.0), ('three', 3)])предварительно> кодовый блок>Если вы обновляете значение данного ключа в упорядоченном словаре, то ключ не перемещается, а присваивается новое значение на месте. Аналогичным образом, если вы используете
.update()для изменения значения существующей пары ключ-значение, то словарь запоминает положение ключа и присваивает ему обновленное значение.Выполнение итерации по
OrderedDictКак и в случае с обычными словарями, вы можете выполнить итерацию по объекту
OrderedDict, используя несколько инструментов и приемов. Вы можете перебирать ключи напрямую или использовать методы со словарем, такие как.items(),.keys(), и.values():>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> # Iterate over the keys directly >>> for key in numbers: ... print(key, "->", numbers[key]) ... one -> 1 two -> 2 three -> 3 >>> # Iterate over the items using .items() >>> for key, value in numbers.items(): ... print(key, "->", value) ... one -> 1 two -> 2 three -> 3 >>> # Iterate over the keys using .keys() >>> for key in numbers.keys(): ... print(key, "->", numbers[key]) ... one -> 1 two -> 2 three -> 3 >>> # Iterate over the values using .values() >>> for value in numbers.values(): ... print(value) ... 1 2 3предварительно> кодовый блок>Первый
forцикл выполняется непосредственно по ключамnumbers. Остальные три цикла используют методы словаря для перебора элементов, ключей и значенийnumbers.Повторение в обратном порядке с
reversed()Еще одной важной особенностью, которую
OrderedDictпредоставляет Python 3.5, является то, что его элементы, ключи и значения поддерживают обратную итерацию с использованиемreversed(). Эта функция была добавлена в обычные словари в Python 3.8. Таким образом, если ваш код использует его, то ваша обратная совместимость с обычными словарями гораздо более ограничена.Вы можете использовать
reversed()с элементами, ключами и значениями объектаOrderedDict:>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> # Iterate over the keys directly in reverse order >>> for key in reversed(numbers): ... print(key, "->", numbers[key]) ... three -> 3 two -> 2 one -> 1 >>> # Iterate over the items in reverse order >>> for key, value in reversed(numbers.items()): ... print(key, "->", value) ... three -> 3 two -> 2 one -> 1 >>> # Iterate over the keys in reverse order >>> for key in reversed(numbers.keys()): ... print(key, "->", numbers[key]) ... three -> 3 two -> 2 one -> 1 >>> # Iterate over the values in reverse order >>> for value in reversed(numbers.values()): ... print(value) ... 3 2 1предварительно> кодовый блок>Каждый цикл в этом примере использует
reversed()для перебора различных элементов упорядоченного словаря в обратном порядке.Обычные словари также поддерживают обратную итерацию. Однако, если вы попытаетесь использовать
reversed()с обычным объектомdictв версии Python ниже 3.8, то получитеTypeError:Python 3.7.9 (default, Jan 14 2021, 11:41:20) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> numbers = dict(one=1, two=2, three=3) >>> for key in reversed(numbers): ... print(key) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'dict' object is not reversibleпредварительно> кодовый блок>Если вам нужно перебрать элементы в словаре в обратном порядке, то
OrderedDict- хороший помощник. Использование обычного словаря значительно снижает вашу обратную совместимость, поскольку обратная итерация не была добавлена в обычные словари до версии Python 3.8.Изучение уникальных возможностей языка Python
OrderedDictНачиная с версии Python 3.6, обычные словари сохраняют свои элементы в том же порядке, в каком они были вставлены в базовый словарь. Как вы уже видели, это ограничивает полезность
OrderedDict. ОднакоOrderedDictпредоставляет некоторые уникальные возможности, которые вы не сможете найти в обычном объектеdict.При использовании упорядоченного словаря у вас есть доступ к следующим дополнительным и расширенным методам:
.move_to_end()добавлен ли новый метод в Python 3.2, который позволяет перемещать существующий элемент либо в в конец или в начало словаря.
.popitem()является расширенной вариацией своего аналогаdict.popitem(), который позволяет вам удалять и возвращать элемент либо из конца, либо из начала базового упорядоченного словаря.
OrderedDictиdictтакже ведут себя по-разному, когда проверяются на равенство. В частности, при сравнении упорядоченных словарей важен порядок элементов. С обычными словарями дело обстоит иначе.Наконец, экземпляры
OrderedDictпредоставляют атрибут с именем.__dict__, который вы не можете найти в экземпляре обычного словаря. Этот атрибут позволяет добавлять пользовательские атрибуты, доступные для записи, в существующий упорядоченный словарь.Изменение порядка Элементов С помощью
.move_to_end()Одно из наиболее примечательных различий между
dictиOrderedDictзаключается в том, что в последнем есть дополнительный метод, называемый.move_to_end(). Этот метод позволяет перемещать существующие элементы либо в конец, либо в начало базового словаря, так что это отличный инструмент для изменения порядка в словаре.Когда вы используете
.move_to_end(), вы можете указать два аргумента:
keyсодержит клавишу, которая идентифицирует элемент, который вы хотите переместить. Еслиkeyне существует, то вы получаетеKeyError.
lastсодержит логическое значение , которое определяет, в какой конец словаря вы хотите переместить элемент под рукой. По умолчанию используется значениеTrue, что означает, что элемент будет перемещен в конец, или правую часть, словаря.Falseозначает, что элемент будет перемещен в начало, или левую часть, упорядоченного словаря.Вот пример того, как использовать
.move_to_end()с аргументомkeyи полагаться на значение по умолчаниюlast:>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3)]) >>> numbers.move_to_end("one") >>> numbers OrderedDict([('two', 2), ('three', 3), ('one', 1)])предварительно> кодовый блок>Когда вы вызываете
.move_to_end()сkeyв качестве аргумента, вы перемещаете имеющуюся пару ключ-значение в конец словаря. Вот почему('one', 1)сейчас находится на последней позиции. Обратите внимание, что остальные элементы остаются в том же первоначальном порядке.Если вы перейдете от
Falseкlast, то переместите элемент в начало:>>> numbers.move_to_end("one", last=False) >>> numbers OrderedDict([('one', 1), ('two', 2), ('three', 3)])предварительно> кодовый блок>В этом случае вы перемещаете
('one', 1)в начало словаря. Это обеспечивает интересную и мощную функцию. Например, с помощью.move_to_end()вы можете отсортировать упорядоченный словарь по ключам:>>> from collections import OrderedDict >>> letters = OrderedDict(b=2, d=4, a=1, c=3) >>> for key in sorted(letters): ... letters.move_to_end(key) ... >>> letters OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)])предварительно> кодовый блок>В этом примере сначала создается упорядоченный словарь,
letters. Циклforвыполняет итерацию по отсортированным ключам и перемещает каждый элемент в конец словаря. Когда цикл завершится, элементы вашего упорядоченного словаря будут отсортированы по ключам.Сортировка словаря по значениям была бы интересным упражнением, поэтому разверните блок ниже и попробуйте!
Отсортируйте следующий словарь по значениям:
>>> from collections import OrderedDict >>> letters = OrderedDict(a=4, b=3, d=1, c=2)предварительно> кодовый блок>как полезную подсказку для реализации решения, рассмотрите возможность использования
lambdaФункция.Вы можете развернуть блок ниже, чтобы увидеть возможное решение.
Вы можете использовать функцию
lambdaдля получения значения каждой пары ключ-значение вlettersи использовать эту функцию в качестве аргументаkeyдляsorted():>>> for key, _ in sorted(letters.items(), key=lambda item: item[1]): ... letters.move_to_end(key) ... >>> letters OrderedDict([('d', 1), ('c', 2), ('b', 3), ('a', 4)])предварительно> кодовый блок>В этом коде вы используете функцию
lambda, которая возвращает значение каждой пары ключ-значение вletters. Вызовsorted()использует эту функциюlambdaдля извлечения ключа сравнения из каждого элемента входного итеративного параметраletters.items(). Затем вы используете.move_to_end()для сортировкиletters.Отлично! Теперь вы знаете, как изменить порядок упорядоченных словарей с помощью
.move_to_end(). Вы готовы перейти к следующему разделу.Удаление элементов С помощью
.popitem()Еще одной интересной особенностью
OrderedDictявляется расширенная версия.popitem(). По умолчанию.popitem()удаляет и возвращает элемент в порядке LIFO (Последний пришедший/первый вышедший). Другими словами, он удаляет элементы из правого конца упорядоченного словаря:>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> numbers.popitem() ('three', 3) >>> numbers.popitem() ('two', 2) >>> numbers.popitem() ('one', 1) >>> numbers.popitem() Traceback (most recent call last): File "<input>", line 1, in <module> numbers.popitem() KeyError: 'dictionary is empty'предварительно> кодовый блок>Здесь вы удаляете все элементы из
Однакоnumbers, используя.popitem(). Каждый вызов этого метода удаляет один элемент из конца базового словаря. Если вы вызовете.popitem()в пустом словаре, то получитеKeyError. До этого момента.popitem()ведет себя так же, как и в обычных словарях.В
OrderedDict.popitem()также принимает логический аргументlast, значение которого по умолчанию равноTrue. Если вы установите дляlastзначениеFalse, то.popitem()удалит элементы в порядке FIFO (первый вход/первый выход), что означает, что он удаляет элементы из начала словаря:>>> from collections import OrderedDict >>> numbers = OrderedDict(one=1, two=2, three=3) >>> numbers.popitem(last=False) ('one', 1) >>> numbers.popitem(last=False) ('two', 2) >>> numbers.popitem(last=False) ('three', 3) >>> numbers.popitem(last=False) Traceback (most recent call last): File "<input>", line 1, in <module> numbers.popitem(last=False) KeyError: 'dictionary is empty'предварительно> кодовый блок>Если для параметра
lastзадано значениеFalse, вы можете использовать.popitem()для удаления и возврата элементов из начала упорядоченного словаря. В этом примере последний вызов.popitem()вызываетKeyError, поскольку базовый словарь уже пуст.Проверка на равенство между словарями
Когда вы проверяете два
OrderedDictобъекта на равенство в логическом контексте, порядок элементов играет важную роль. Например, если ваши упорядоченные словари содержат один и тот же набор элементов, то результат теста зависит от их порядка:>>> from collections import OrderedDict >>> letters_0 = OrderedDict(a=1, b=2, c=3, d=4) >>> letters_1 = OrderedDict(b=2, a=1, c=3, d=4) >>> letters_2 = OrderedDict(a=1, b=2, c=3, d=4) >>> letters_0 == letters_1 False >>> letters_0 == letters_2 Trueпредварительно> кодовый блок>В этом примере
letters_1имеет небольшое отличие в порядке следования элементов по сравнению сletters_0иletters_2, поэтому первый тест возвращаетFalse. Во втором тестеletters_0иletters_2содержат одинаковый набор элементов, расположенных в одинаковом порядке, поэтому тест возвращаетTrue.Если вы попробуете повторить этот же пример с помощью обычных словарей, то получите другой результат:
>>> letters_0 = dict(a=1, b=2, c=3, d=4) >>> letters_1 = dict(b=2, a=1, c=3, d=4) >>> letters_2 = dict(a=1, b=2, c=3, d=4) >>> letters_0 == letters_1 True >>> letters_0 == letters_2 True >>> letters_0 == letters_1 == letters_2 Trueпредварительно> кодовый блок>Здесь, когда вы проверяете два обычных словаря на равенство, вы получаете
True, если оба словаря содержат одинаковый набор элементов. В этом случае порядок элементов не влияет на конечный результат.Наконец, тесты на равенство между объектом
OrderedDictи обычным словарем не учитывают порядок элементов:>>> from collections import OrderedDict >>> letters_0 = OrderedDict(a=1, b=2, c=3, d=4) >>> letters_1 = dict(b=2, a=1, c=3, d=4) >>> letters_0 == letters_1 Trueпредварительно> кодовый блок>При сравнении упорядоченных словарей с обычными порядок элементов не имеет значения. Если оба словаря содержат одинаковый набор элементов, то они сравниваются одинаково, независимо от порядка их элементов.
Добавление новых атрибутов к экземпляру словаря
OrderedDictобъекты имеют атрибут.__dict__, который вы не можете найти в обычных словарных объектах. Взгляните на следующий код:>>> from collections import OrderedDict >>> letters = OrderedDict(b=2, d=4, a=1, c=3) >>> letters.__dict__ {} >>> letters1 = dict(b=2, d=4, a=1, c=3) >>> letters1.__dict__ Traceback (most recent call last): File "<input>", line 1, in <module> letters1.__dict__ AttributeError: 'dict' object has no attribute '__dict__'предварительно> кодовый блок>В первом примере вы получаете доступ к атрибуту
.__dict__в упорядоченном словареletters. Python внутренне использует этот атрибут для хранения доступных для записи атрибутов экземпляра. Во втором примере показано, что обычные словарные объекты не имеют атрибута.__dict__.Вы можете использовать атрибут упорядоченного словаря
.__dict__для хранения динамически создаваемых атрибутов экземпляра, доступных для записи. Существует несколько способов сделать это. Например, вы можете использовать назначение в стиле словаря, как вordered_dict.__dict__["attr"] = value. Вы также можете использовать точечную запись, как вordered_dict.attr = value.Вот пример использования
.__dict__для присоединения новой функции к существующему упорядоченному словарю:>>> from collections import OrderedDict >>> letters = OrderedDict(b=2, d=4, a=1, c=3) >>> letters.sorted_keys = lambda: sorted(letters.keys()) >>> vars(letters) {'sorted_keys': <function <lambda> at 0x7fa1e2fe9160>} >>> letters.sorted_keys() ['a', 'b', 'c', 'd'] >>> letters["e"] = 5 >>> letters.sorted_keys() ['a', 'b', 'c', 'd', 'e']предварительно> кодовый блок>Теперь у вас есть
.sorted_keys()lambdaфункция, подключенная к вашемуlettersупорядоченному словарю. Обратите внимание, что вы можете просмотреть содержимое.__dict__, либо обратившись к нему напрямую с помощью точечной записи, либо используяvars().Примечание: Этот тип динамического атрибута добавляется к конкретному экземпляру данного класса. В приведенном выше примере этим экземпляром является
letters. Это не влияет ни на другие экземпляры, ни на сам класс, поэтому у вас есть доступ к.sorted_keys()только черезletters.Вы можете использовать эту динамически добавляемую функцию для перебора ключей словаря в отсортированном порядке без изменения исходного порядка в
letters:>>> for key in letters.sorted_keys(): ... print(key, "->", letters[key]) ... a -> 1 b -> 2 c -> 3 d -> 4 e -> 5 >>> letters OrderedDict([('b', 2), ('d', 4), ('a', 1), ('c', 3), ('e', 5)])предварительно> кодовый блок>Это всего лишь пример того, насколько полезной может быть функция
OrderedDict. Обратите внимание, что вы не можете сделать что-то подобное с обычным словарем:>>> letters = dict(b=2, d=4, a=1, c=3) >>> letters.sorted_keys = lambda: sorted(letters.keys()) Traceback (most recent call last): File "<input>", line 1, in <module> letters.sorted_keys = lambda: sorted(letters.keys()) AttributeError: 'dict' object has no attribute 'sorted_keys'предварительно> кодовый блок>Если вы попытаетесь динамически добавить пользовательские атрибуты экземпляра в обычный словарь, то получите сообщение
AttributeError, сообщающее вам, что в базовом словаре нет соответствующего атрибута. Это связано с тем, что в обычных словарях нет атрибута.__dict__для хранения новых атрибутов экземпляра.Объединение и обновление словарей с помощью операторов
Python 3.9 добавил два новых оператора в пространство словаря. Теперь вам нужно объединить (
|) и обновить (|=) операторы словаря. Эти операторы также работают сOrderedDictэкземплярами:Python 3.9.0 (default, Oct 5 2020, 17:52:02) [GCC 9.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from collections import OrderedDict >>> physicists = OrderedDict(newton="1642-1726", einstein="1879-1955") >>> biologists = OrderedDict(darwin="1809-1882", mendel="1822-1884") >>> scientists = physicists | biologists >>> scientists OrderedDict([ ('newton', '1642-1726'), ('einstein', '1879-1955'), ('darwin', '1809-1882'), ('mendel', '1822-1884') ])предварительно> кодовый блок>Как следует из названия, оператор слияния объединяет два словаря в новый, содержащий элементы обоих исходных словарей. Если словари в выражении имеют общие ключи, то значения крайнего правого словаря будут преобладать.
Оператор update удобен, когда у вас есть словарь и вы хотите обновить некоторые его значения без вызова
.update():>>> physicists = OrderedDict(newton="1642-1726", einstein="1879-1955") >>> physicists_1 = OrderedDict(newton="1642-1726/1727", hawking="1942-2018") >>> physicists |= physicists_1 >>> physicists OrderedDict([ ('newton', '1642-1726/1727'), ('einstein', '1879-1955'), ('hawking', '1942-2018') ])предварительно> кодовый блок>В этом примере вы используете оператор обновления словаря для обновления информации о времени жизни Ньютона . Оператор обновляет словарь на месте. Если словарь, предоставляющий обновленные данные, содержит новые ключи, то эти ключи добавляются в конец исходного словаря.
Учитывая производительность
Производительность - важный вопрос в программировании. Обычно важно знать, насколько быстро работает алгоритм или сколько памяти он использует.
OrderedDictизначально был закодирован на Python, а затем написан на C, чтобы максимально повысить эффективность его методов и операций. Эти две реализации в настоящее время доступны в стандартной библиотеке. Однако реализация на Python служит альтернативой, если по какой-либо причине недоступна реализация на C.Обе реализации
OrderedDictпредполагают использование двусвязного списка для определения порядка элементов. Несмотря на то, что для некоторых операций требуется линейное время, реализация связанного списка вOrderedDictсильно оптимизирована для сохранения быстрого времени работы соответствующих методов словаря. Тем не менее, операции с упорядоченным словарем являются O(1), но с большим постоянным коэффициентом по сравнению с обычными словарями.В целом,
OrderedDictимеет более низкую производительность, чем обычные словари. Вот пример, в котором измеряется время выполнения нескольких операций в обоих классах словарей:# time_testing.py from collections import OrderedDict from time import perf_counter def average_time(dictionary): time_measurements = [] for _ in range(1_000_000): start = perf_counter() dictionary["key"] = "value" "key" in dictionary "missing_key" in dictionary dictionary["key"] del dictionary["key"] end = perf_counter() time_measurements.append(end - start) return sum(time_measurements) / len(time_measurements) * int(1e9) ordereddict_time = average_time(OrderedDict.fromkeys(range(1000))) dict_time = average_time(dict.fromkeys(range(1000))) gain = ordereddict_time / dict_time print(f"OrderedDict: {ordereddict_time:.2f} ns") print(f"dict: {dict_time:.2f} ns ({gain:.2f}x faster)")предварительно> кодовый блок>В этом сценарии вы вычисляете
average_time(), необходимое для выполнения нескольких распространенных операций с данным словарем. Циклforиспользуетtime.perf_counter()для измерения времени выполнения набора операций. Функция возвращает среднее время в наносекундах, необходимое для выполнения выбранного набора операций.Примечание: Если вам интересно узнать о других способах определения времени выполнения вашего кода, вы можете ознакомиться с Функциями таймера Python: три способа отслеживания вашего кода.
Если вы запустите этот скрипт из командной строки, то получите результат, аналогичный этому:
$ python time_testing.py OrderedDict: 272.93 ns dict: 197.88 ns (1.38x faster)предварительно> кодовый блок>Как вы видите в выходных данных, операции с объектами
dictвыполняются быстрее, чем операции с объектамиOrderedDict.Что касается потребления памяти,
OrderedDictэкземпляры должны платить за хранение из-за упорядоченного списка ключей. Вот сценарий, который дает вам представление об этой стоимости памяти:>>> import sys >>> from collections import OrderedDict >>> ordereddict_memory = sys.getsizeof(OrderedDict.fromkeys(range(1000))) >>> dict_memory = sys.getsizeof(dict.fromkeys(range(1000))) >>> gain = 100 - dict_memory / ordereddict_memory * 100 >>> print(f"OrderedDict: {ordereddict_memory} bytes") OrderedDict: 85408 bytes >>> print(f"dict: {dict_memory} bytes ({gain:.2f}% lower)") dict: 36960 bytes (56.73% lower)предварительно> кодовый блок>В этом примере вы используете
sys.getsizeof()для измерения объема памяти в байтах двух объектов справочника. В выходных данных вы можете видеть, что обычный словарь занимает меньше памяти, чем егоOrderedDictаналог.Выбор подходящего словаря для задания
Итак, вы узнали о тонких различиях между
OrderedDictиdict. Вы узнали, что, несмотря на то, что обычные словари стали упорядоченными структурами данных начиная с версии Python 3.6, все еще есть некоторая ценность в использованииOrderedDictиз-за набора полезных функций, которых нет в Python 3.6.dict.Вот краткое описание наиболее важных различий и особенностей обоих классов, которые вам следует учитывать при принятии решения о том, какой из них использовать:
Feature OrderedDictdictKey insertion order maintained Yes (since Python 3.1) Yes (since Python 3.6) Readability and intent signaling regarding the order of items High Low Control over the order of items High ( .move_to_end(), enhanced.popitem())Low (removing and reinserting items is required) Operations performance Low High Memory consumption High Low Equality tests consider the order of items Yes No Support for reverse iteration Yes (since Python 3.5) Yes (since Python 3.8) Ability to append new instance attributes Yes ( .__dict__attribute)No Support for the merge ( |) and update (|=) dictionary operatorsYes (since Python 3.9) Yes (since Python 3.9) В этой таблице приведены некоторые основные различия между
OrderedDictиdict, которые следует учитывать, когда вам нужно выбрать класс словаря для решения задачи или реализации определенного алгоритма. В общем, если порядок элементов в словаре важен для корректной работы вашего кода, то в первую очередь следует обратить внимание наOrderedDict.Построение очереди на основе словаря
Вариант использования, в котором вам следует рассмотреть возможность использования объекта
OrderedDict, а не объектаdict, - это когда вам нужно реализовать очередь на основе словаря. Очереди - это распространенные и полезные структуры данных, которые управляют своими элементами в режиме FIFO. Это означает, что вы помещаете новые элементы в конец очереди, а старые элементы выводите из начала очереди.Обычно очереди реализуют операцию добавления элемента в свой конец, которая известна как операция постановки в очередь. Очереди также реализуют операцию удаления элементов с самого начала, которая известна как операция удаления из очереди.
Чтобы создать очередь на основе словаря, запустите редактор кода или IDE, создайте новый модуль Python с именем
queue.pyи добавьте в него следующий код:# queue.py from collections import OrderedDict class Queue: def __init__(self, initial_data=None, /, **kwargs): self.data = OrderedDict() if initial_data is not None: self.data.update(initial_data) if kwargs: self.data.update(kwargs) def enqueue(self, item): key, value = item if key in self.data: self.data.move_to_end(key) self.data[key] = value def dequeue(self): try: return self.data.popitem(last=False) except KeyError: print("Empty queue") def __len__(self): return len(self.data) def __repr__(self): return f"Queue({self.data.items()})"предварительно> кодовый блок>В
Queueсначала вы инициализируете атрибут экземпляра , называемый.data. Этот атрибут содержит пустой упорядоченный словарь, который вы будете использовать для хранения данных. Инициализатор класса принимает первый необязательный аргументinitial_data, который позволяет вам предоставить исходные данные при создании экземпляра класса. Инициализатор также принимает необязательные аргументы ключевого слова (kwargs), чтобы вы могли использовать аргументы ключевого слова в конструкторе.Затем вы вводите код
.enqueue(), который позволяет добавлять пары ключ-значение в очередь. В этом случае вы используете.move_to_end(), если ключ уже существует, и вы используете обычное назначение для новых ключей. Обратите внимание, что для работы этого метода вам необходимо предоставить два элементаtupleилиlistс допустимой парой ключ-значение.Реализация
.dequeue()использует.popitem()сlastравнымFalseдля удаления и возврата элементов из начала базового упорядоченного словаря,.data. В этом случае вы используетеtry...exceptблок для обработкиKeyError, который возникает при вызове.popitem()в пустом словаре.Специальный метод ,
.__len__(), предоставляет необходимую функциональность для получения длины внутреннего упорядоченного словаря,.data. Наконец, специальный метод.__repr__()обеспечивает удобное для пользователя строковое представление очереди при выводе структуры данных на экран.Вот несколько примеров того, как вы можете использовать
Queue:>>> from queue import Queue >>> # Create an empty queue >>> empty_queue = Queue() >>> empty_queue Queue(odict_items([])) >>> # Create a queue with initial data >>> numbers_queue = Queue([("one", 1), ("two", 2)]) >>> numbers_queue Queue(odict_items([('one', 1), ('two', 2)])) >>> # Create a queue with keyword arguments >>> letters_queue = Queue(a=1, b=2, c=3) >>> letters_queue Queue(odict_items([('a', 1), ('b', 2), ('c', 3)])) >>> # Add items >>> numbers_queue.enqueue(("three", 3)) >>> numbers_queue Queue(odict_items([('one', 1), ('two', 2), ('three', 3)])) >>> # Remove items >>> numbers_queue.dequeue() ('one', 1) >>> numbers_queue.dequeue() ('two', 2) >>> numbers_queue.dequeue() ('three', 3) >>> numbers_queue.dequeue() Empty queueпредварительно> кодовый блок>В этом примере кода вы сначала создаете три разных объекта
Queue, используя разные подходы. Затем вы используете.enqueue(), чтобы добавить один элемент в конецnumbers_queue. Наконец, вы вызываете.dequeue()несколько раз, чтобы удалить все элементы изnumbers_queue. Обратите внимание, что при последнем вызове.dequeue()на экран выводится сообщение, информирующее вас о том, что очередь уже пуста.Заключение
В течение многих лет словари Python представляли собой неупорядоченные структуры данных. Это выявило необходимость в упорядоченном словаре, который помогает в ситуациях, когда важен порядок элементов . Поэтому разработчики Python создали
OrderedDict,, который был специально разработан для упорядочивания элементов.В Python 3.6 появилась новая функция для обычных словарей. Теперь они также запоминают порядок элементов. После этого добавления большинство программистов на Python задаются вопросом, стоит ли им по-прежнему использовать
OrderedDict.В этом уроке вы узнали:
- Как создавать и использовать
OrderedDictобъекты в вашем коде- В чем основные различия между
OrderedDictиdict- В чем плюсы и минусы использования
OrderedDictпротивdictТеперь вы в лучшем положении, чтобы принять обоснованное решение о том, использовать ли
dictилиOrderedDict, если вашему коду нужен упорядоченный словарь.В этом руководстве вы написали пример того, как реализовать очередь на основе словаря, который является примером использования, показывающим, что
<статус завершения article-slug="python-ordereddict" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/статьи/python-ordereddict/bookmark/" статус завершения data-api-article-url="/api/v1/articles/python-ordereddict/завершение_статуса/"> статус завершения> <поделиться-кнопка bluesky-text="Интересная статья о Python от @realpython.com:" email-body="Ознакомьтесь с этой статьей о Python:%0A%0AOrderedDict против dict в Python: Подходящий инструмент для работы" email-subject="Статья на Python для вас" twitter-text="Интересная статья на #Python от @realpython:" url="https://realpython.com/python-ordereddict/" url-title="OrderedDict против dict в Python: Подходящий инструмент для работы"> кнопка поделиться>OrderedDictвсе еще может быть полезен в ваших ежедневных приключениях по написанию кода на Python.Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Использование OrderedDict в Python
Back to Top