Переопределить нотацию {}, чтобы я получил OrderedDict() вместо dict()?

Обновление: словари, сохраняющие порядок вставки, гарантированы для Python 3.7+

Я хочу использовать файл .py как файл конфигурации. Таким образом, используя нотацию {...}, я могу создать словарь, используя строки в качестве ключей, но порядок определения теряется в стандартном словаре Python.

Мой вопрос: можно ли переопределить обозначение {...}, чтобы я получил OrderedDict() вместо dict()?

Я надеялся, что простое переопределение конструктора dict с помощью OrderedDict (dict = OrderedDict) сработает, но это не так.

Eg:

dict = OrderedDict
dictname = {
   'B key': 'value1',
   'A key': 'value2',
   'C key': 'value3'
   }

print dictname.items()

Вывод:

[('B key', 'value1'), ('A key', 'value2'), ('C key', 'value3')]

person fdb    schedule 24.10.2011    source источник
comment
Я предполагаю, что упомянутый здесь вывод - это то, что вам нужно, а не то, что происходит?   -  person Tony Suffolk 66    schedule 16.08.2016
comment
К сведению людей, которые в 2016 году наткнулись на этот 5-летний вопрос: начиная с Python 3.6 все dict сохраняют порядок вставки, поэтому в будущем ни один из этих хаков не понадобится.   -  person Nick Sweeting    schedule 17.12.2016
comment
@NickSweeting docs.python.org/3/whatsnew/3.6. html#new-dict-implementation говорит, что аспект сохранения порядка в этой новой реализации считается деталью реализации, и на него нельзя полагаться.   -  person Samuel    schedule 21.06.2017
comment
@ Сэмюэл Сантана, то, как я прочитал остальную часть предложения, которое вы процитировали в начале, предполагает, что это новое сохранение порядка является долгосрочной желаемой семантикой для языка, не привязываясь к ней прямо сейчас.   -  person Adam Kerz    schedule 09.01.2018
comment
Начиная с версии 3.7, на эту семантику можно полагаться   -  person Eric    schedule 25.08.2018
comment
@NickSweeting: Как уже отмечалось, это языковая гарантия версии 3.7 (только детали реализации в версии 3.6). Однако эти хаки могут быть полезны для получения уникальных методов OrderedDict; Способность popitem всплывать в режиме FIFO недоступна на dict, а move_to_end вообще недоступна (вы можете имитировать режим last=True с mydict[key] = mydict.pop(key), но это немного дороже, а режим last=False недоступен). В версиях 3.6-3.7 также отсутствует возможность перебирать dict и его представления в обратном порядке, хотя 3.8 (вероятно) добавляет эту возможность< /а>.   -  person ShadowRanger    schedule 28.09.2018


Ответы (7)


Вот хак, который почти дает вам нужный синтаксис:

class _OrderedDictMaker(object):
    def __getitem__(self, keys):
        if not isinstance(keys, tuple):
            keys = (keys,)
        assert all(isinstance(key, slice) for key in keys)

        return OrderedDict([(k.start, k.stop) for k in keys])

ordereddict = _OrderedDictMaker()
from nastyhacks import ordereddict

menu = ordereddict[
   "about" : "about",
   "login" : "login",
   'signup': "signup"
]

Изменить: кто-то другой обнаружил это независимо и опубликовал odictliteral пакет на PyPI, обеспечивающий чуть более полную реализацию — вместо него используйте этот пакет

person Eric    schedule 16.05.2016
comment
содрогаюсь - я понимаю, почему вы называете это хаком - пожалуйста - не используйте это в продакшене - person Tony Suffolk 66; 16.08.2016
comment
Это гениально. Злой гений. - person Régis B.; 16.08.2016
comment
@RégisB.: Сегодня этот пост привлек много внимания — была ли ссылка на него где-то еще? - person Eric; 16.08.2016
comment
@Eric Я пришел сюда из Reddit reddit.com/ р/Питон/комментарии/4xyfh7/ - person Tony Baguette; 16.08.2016
comment
TIL мы можем использовать строки с объектом среза. - person Ashwini Chaudhary; 17.08.2016
comment
Чтобы понять, как это работает, см. stackoverflow.com/questions. /2936863/ - person Jesse Vogt; 19.08.2016
comment
@ Эрик, это не работает, если одна клавиша: меню = упорядоченный дикт[о: о]; но кажется это легко исправить. - person TatianaP; 13.07.2017
comment
@TatianaP: Хороший улов, исправлено. Но в любом случае используйте не мою версию pypi! - person Eric; 13.07.2017

Чтобы буквально получить то, что вы просите, вам нужно повозиться с синтаксическим деревом вашего файла. Я не думаю, что это целесообразно делать, но я не мог устоять перед искушением попробовать. Итак, поехали.

Во-первых, мы создаем модуль с функцией my_execfile(), которая работает так же, как встроенная execfile(), за исключением того, что все вхождения словаря отображаются, например. {3: 4, "a": 2} заменяются явными вызовами конструктора dict(), например dict([(3, 4), ('a', 2)]). (Конечно, мы могли бы напрямую заменить их вызовами collections.OrderedDict(), но мы не хотим быть слишком навязчивыми.) Вот код:

import ast

class DictDisplayTransformer(ast.NodeTransformer):
    def visit_Dict(self, node):
        self.generic_visit(node)
        list_node = ast.List(
            [ast.copy_location(ast.Tuple(list(x), ast.Load()), x[0])
             for x in zip(node.keys, node.values)],
            ast.Load())
        name_node = ast.Name("dict", ast.Load())
        new_node = ast.Call(ast.copy_location(name_node, node),
                            [ast.copy_location(list_node, node)],
                            [], None, None)
        return ast.copy_location(new_node, node)

def my_execfile(filename, globals=None, locals=None):
    if globals is None:
        globals = {}
    if locals is None:
        locals = globals
    node = ast.parse(open(filename).read())
    transformed = DictDisplayTransformer().visit(node)
    exec compile(transformed, filename, "exec") in globals, locals

С помощью этой модификации мы можем изменить поведение отображения словаря, перезаписав dict. Вот пример:

# test.py
from collections import OrderedDict
print {3: 4, "a": 2}
dict = OrderedDict
print {3: 4, "a": 2}

Теперь мы можем запустить этот файл, используя my_execfile("test.py"), что приведет к выводу

{'a': 2, 3: 4}
OrderedDict([(3, 4), ('a', 2)])

Обратите внимание, что для простоты приведенный выше код не затрагивает включение словаря, которое должно быть преобразовано в выражения генератора, переданные конструктору dict(). Вам нужно добавить метод visit_DictComp() в класс DictDisplayTransformer. Учитывая приведенный выше пример кода, это должно быть прямолинейно.

Опять же, я не рекомендую возиться с семантикой языка. Вы заглянули в модуль ConfigParser?

person Sven Marnach    schedule 24.10.2011
comment
Да, я буду использовать ConfigParser... но ваше решение многообещающее. Большое Вам спасибо. - person fdb; 25.10.2011
comment
@fdb — прежде чем вы подумаете об изменении семантики языка — подумайте о принципе «Явное лучше, чем неявное» — если вы попытаетесь переопределить «{}» или скрыть, чтобы не вводить «OrderedDict», — вы в конечном итоге сделаете свой код намного труднее читать для других - или для себя через 6 месяцев. Просто введите «OrderedDict» — он понятен и делает то, что вы хотите — больше печатать, но с улучшенной читабельностью. - person Tony Suffolk 66; 16.08.2016

OrderedDict не является «стандартным синтаксисом Python», однако упорядоченный набор пар ключ-значение (в стандартном синтаксисе Python) просто:

[('key1 name', 'value1'), ('key2 name', 'value2'), ('key3 name', 'value3')]

Чтобы явно получить OrderedDict:

OrderedDict([('key1 name', 'value1'), ('key2 name', 'value2'), ('key3 name', 'value3')])

Другой альтернативой является сортировка dictname.items(), если это все, что вам нужно:

sorted(dictname.items())
person Austin Marshall    schedule 24.10.2011
comment
мой вопрос не в том, является ли OrderedDict стандартным синтаксисом python, а в том, можно ли переопределить нотацию {...} - person fdb; 24.10.2011
comment
@fdb: в Python {} создает объект dict, который по определению неупорядочен. Вы можете, конечно, определить свой собственный язык с помощью {}, обозначающего упорядоченный словарь. Вы даже можете написать небольшую оболочку, которая переводит ваш новый язык на Python. Это то, чего вы на самом деле хотите? - person Sven Marnach; 24.10.2011
comment
@SvenMarnach: да! но надеялся, что простое переопределение конструктора dict с помощью OrderedDict (dict = OrderedDict) сработает. - person fdb; 24.10.2011
comment
@fdb: это работает, только если вы создадите свой словарь, вызвав dict() - person Daenyth; 24.10.2011
comment
sorted(dictname.items()) дает элементы в возрастающем лексическом порядке ключей. OrderedDict порядок определяется порядком вставки, а не ключевым лексическим порядком. Возможно, вы думаете о SortedDict (которого в настоящее время не существует). - person PaulMcG; 16.08.2016
comment
прежде чем вы подумаете об изменении семантики языка — подумайте о принципе «Явное лучше, чем неявное» — если вы попытаетесь переопределить «{}» или скрыть, чтобы не набирать «OrderedDict», — вы в конечном итоге сделаете свой код намного более трудно читать для других - или для себя через 6 месяцев. Просто введите «OrderedDict» — он понятен и делает то, что вы хотите — больше печатать, но с улучшенной читабельностью. - person Tony Suffolk 66; 16.08.2016

Начиная с python 3.6, все словари будут упорядочены по по умолчанию. На данный момент это деталь реализации dict, и на нее нельзя полагаться, но, вероятно, она станет стандартной после версии 3.6.

Порядок вставки всегда сохраняется в новой реализации dict:

>>>x = {'a': 1, 'b':2, 'c':3 }
>>>list(x.keys())
['a', 'b', 'c']

Начиная с python 3.6 **kwargs порядок [PEP468] и порядок атрибутов класса [PEP520]. Новая компактная, упорядоченная реализация словаря используется для реализации упорядочения для обоих из них.

person Nick Sweeting    schedule 17.12.2016
comment
Возможно, это связано с тем, что docs.python.org/3 /whatsnew/3.6.html#new-dict-implementation говорит: Аспект этой новой реализации, сохраняющий порядок, считается деталью реализации, и на него нельзя полагаться. Тем не менее, я нашел информацию интересной, так что вот голос! - person Samuel; 21.06.2017
comment
Обновление: сохранение порядка вставки теперь является стандартным в 3.7, и на него можно положиться. - person Nick Sweeting; 30.01.2019

То, о чем вы просите, невозможно, но если файла конфигурации в синтаксисе JSON достаточно, вы можете что-то сделать аналогично с модулем json:

>>> import json, collections
>>> d = json.JSONDecoder(object_pairs_hook = collections.OrderedDict)
>>> d.decode('{"a":5,"b":6}')
OrderedDict([(u'a', 5), (u'b', 6)])
person Magnus Hoff    schedule 24.10.2011
comment
Невозможное может быть слишком сильным словом - см. мой ответ. - person Sven Marnach; 24.10.2011
comment
@Sven: Да, мне очень понравился твой ответ! :) Думаю, я оставлю свою формулировку в силе. Пожалуйста, скорректируйте свое понимание невозможного в данном контексте, чтобы оно соответствовало действительности ;) - person Magnus Hoff; 24.10.2011
comment
json.loads и json.load также были обновлены, начиная с Python 3.1, с поддержкой object_pairs_hook docs.python.org/3.4/library/json.html#json.load - person Alex Bitek; 27.10.2014

Единственное решение, которое я нашел, - это исправить сам python, заставив объект dict запомнить порядок вставки.

Затем это работает для всех видов синтаксисов:

x = {'a': 1, 'b':2, 'c':3 }
y = dict(a=1, b=2, c=3)

и Т. Д.

Я взял реализацию ordereddict C с сайта https://pypi.python.org/pypi/ruamel.ordereddict/ и снова влился в основной код Python.

Если вы не возражаете против пересборки интерпретатора Python, вот патч для Python 2.7.8: https://github.com/fwyzard/cpython/compare/2.7.8...ordereddict-2.7.8.diff .A

person fwyzard    schedule 03.12.2014
comment
Начиная с 2016/12 года реализация Pypy станет стандартной реализацией Python dict, хорошая работа, предсказывающая это на 2 года вперед! - person Nick Sweeting; 17.12.2016

Если то, что вы ищете, — это способ получить простой в использовании синтаксис инициализации — рассмотрите возможность создания подкласса OrderedDict и добавления к нему операторов, которые обновляют dict, например:

from collections import OrderedDict

class OrderedMap(OrderedDict):
    def __add__(self,other):
        self.update(other)
        return self

d = OrderedMap()+{1:2}+{4:3}+{"key":"value"}

d будет OrderedMap([(1, 2), (4, 3), ('ключ','значение')])


Другой возможный пример синтаксического сахара с использованием синтаксиса нарезки:

class OrderedMap(OrderedDict):
    def __getitem__(self, index):
        if isinstance(index, slice):
            self[index.start] = index.stop 
            return self
        else:
            return OrderedDict.__getitem__(self, index)

d = OrderedMap()[1:2][6:4][4:7]["a":"H"]
person Or Weis    schedule 17.09.2014
comment
Примечание. Оба они сильно нарушают ожидания своих операторов. И __add__, и __getitem__ предназначены для того, чтобы не изменяться, и ожидается, что поддержка срезов будет совокупной формой поддержки индексации, а не полностью несвязанным поведением. Нарушение этих ожиданий равносильно напрашиванию кошмара ремонтопригодности. Взлом slice гораздо лучше использовать для достижения результата, указанного в принятом ответе, где это фабричный объект, который делает обычный OrderedDict, а не замену OrderedDict с продолжающимся странным поведением. - person ShadowRanger; 28.09.2018