Как может 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)])