Как может OrderedDict узнать о порядке элементов уже созданного dict?

Я играл с типом OrderedDict в Python 3.6 и был удивлен его поведением. Когда я создаю простой dict, как это в IPython:

d = dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])

Я получаю:

{'guido': 4127, 'jack': 4098, 'sape': 4139}

как результат, который по какой-то причине не сохраняет порядок элементов при создании экземпляра. Теперь, когда я создаю OrderedDict из d следующим образом:

od = OrderedDict(d)

вывод:

OrderedDict([('sape', 4139), ('guido', 4127), ('jack', 4098)])

Теперь я спрашиваю себя: как может OrderedDict -структор узнать о порядке элементов при создании d? И всегда ли он ведет себя одинаково, так что я могу полагаться на порядок элементов в OrderedDict?

Я уже читал документы Python о словарях и OrderedDict, но я не нашел ответа на свой вопрос.

Выход из (sys.version):

In[22]: sys.version
Out[22]: '3.6.1 (default, Apr  4 2017, 09:40:21) \n[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)]'

Ответ 1

Теперь очевидно, что пользовательский крючок (sys.displayhook), который IPython использует для вывода вывода, - это довольно печатные вещи (используя собственный собственный принтер).

С помощью прямого вызова displayhook вы можете увидеть, как он разрушает порядок вставки:

In [1]: from sys import displayhook
   ...: displayhook({'1': 0, '0': 1})
Out[1]: {'0': 1, '1': 0}

Кроме того, если вы вместо этого захватили словарь str (отправив строку, которая будет отображаться вместо объекта dict), вы получите правильный и ожидаемый порядок:

In [2]: d = dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   ...: d
Out[2]: {'guido': 4127, 'jack': 4098, 'sape': 4139}

In [3]: str(dict(t))
Out[3]: "{'sape': 4139, 'guido': 4127, 'jack': 4098}"

аналогично print ing.

Я не уверен, почему IPython делает это с помощью 3.6, это было довольно запутанно (отредактируйте: см. релевантную проблему на GitHub), В стандартном Python REPL это поведение не будет проявляться, так как sys.displayhook не используется для красивой печати. ​​


Созданный dict d поддерживает порядок вставки, поэтому OrderedDict поддерживает тот же порядок.

Тот факт, что он делает, конечно, представляет собой деталь реализации. Пока это не изменится (и, похоже, это будет так), вы должны придерживаться OrderedDict, чтобы надежно поддерживать порядок между реализациями.


Кстати, если вы хотите, чтобы это было отключено, вы можете запустить IPython с опцией --no-pprint, которая отключит его красивый принтер:

➜ ipython --no-banner --no-pprint 

In [1]: dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
Out[1]: {'sape': 4139, 'guido': 4127, 'jack': 4098}

Ответ 2

В 3.6, в качестве детали реализации, все dict упорядочены. Вас обманывает IPython: до 3.6 порядок ключей был произвольным, поэтому для удобства пользователя интерактивный вывод IPython для dict и set (где обычный Python просто печатает repr) сортирует ключи, Поэтому ваш dict представляется в алфавитном порядке. Возможно, IPython может в конечном итоге отказаться от этого поведения при работе на 3.6+, поскольку, как вы заметили, это довольно запутанно.

Если вы явно print, вместо того чтобы полагаться на ipython для вывода результатов предыдущего выражения для вас, вы обойдете магию ipython REPL и увидите "естественный" порядок. То же самое касается практически любых других способов взаимодействия с dict, поскольку итерация будет выполняться в ожидаемом порядке.

Ответ 3

Как вы, наверное, знаете, словари в Python не упорядочены в соответствии с спецификацией языка. Они имеют свойственный порядок, но этот порядок произволен.

Поэтому, когда вы передаете стандартный словарь в конструктор OrderedDict, новый OrderedDict будет заполняться из значений исходного словаря, итерируя его значения. Таким образом, будет использоваться встроенный порядок словаря, и это будет то, что вы увидите в последнем OrderedDict.

Теперь, с Python 3.6, произошла смена версии словаря по умолчанию. Как обсуждалось и объяснено на этом вопросе, стандартные словари теперь сохраняют порядок вставки. Вот почему, когда вы создали OrderedDict из Python 3.6 dict, исходный порядок также сохраняется.

Означает ли это, что OrderedDict становится устаревшим в Python 3.6+? Нет, поскольку порядок хранения стандартных словарей - это детализация реализации. Вместо произвольного порядка предыдущих реализаций новый словарь просто имеет "правильный" порядок. Но это никоим образом не гарантируется спецификацией языка и может быть или не быть в случае других реализаций. Таким образом, вы не можете и не должны полагаться на него.

Btw. обратите внимание, что Python 3.6 (язык, а не только реализация) гарантирует, что порядок аргументов ключевого слова до OrderedDict сохраняется. Например. это сохраняет порядок:

>>> OrderedDict(sape=4139, guido=4127, jack=4098)
OrderedDict([('sape', 4139), ('guido', 4127), ('jack', 4098)])