如何重写 "{...}" 表示法以获得 OrderedDict() 而不是 dict()?

43

更新:在Python 3.7+中,保留插入顺序的字典是有保障的。

我想使用一个.py文件作为配置文件。 使用{...}符号,我可以创建一个以字符串为键的字典,但在标准python字典中定义的顺序会丢失。

我的问题是:是否有可能覆盖{...}符号,以便我得到一个OrderedDict()而不是dict()

我希望简单地用OrderedDict(dict = OrderedDict)来覆盖dict构造函数,但这并不起作用。

例如:

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

print dictname.items()

输出:

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

我假设这里提到的输出是您想要的,而不是实际发生的? - Tony Suffolk 66
6
提醒那些在2016年看到这个五年老问题的人:从Python 3.6开始,所有的dict都会保留插入顺序,所以今后将不再需要使用这些技巧。 - Nick Sweeting
@NickSweeting https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation 表示:“这个新实现的有序特性被认为是一个实现细节,不应该依赖它”。 - Samuel
@Samuel Santana 我阅读你引用的句子剩下的部分的方式表明,这种新的顺序保留是语言的长期期望语义,但现在还没有承诺。 - Adam Kerz
3
从3.7版本开始,这些语义可以被依赖。 - Eric
@NickSweeting:正如所述,这是3.7的语言保证(在3.6中仅是实现细节)。这些黑客可能仍然有用,以获取OrderedDict的唯一方法; popitem以FIFO模式弹出的能力在dict上不可用,并且根本不可用move_to_end(您可以使用mydict[key] = mydict.pop(key)模拟last=True模式,但它略微更昂贵,而last=False模式不可用)。 3.6-3.7还缺少迭代dict及其视图的能力,尽管3.8(可能)正在添加该功能 - ShadowRanger
7个回答

78
这里有一个小技巧,几乎可以给你想要的语法:
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"
]

编辑:有人独立发现了此方法,并在PyPI上发布了odictliteral包,提供了稍微更全面的实现-请使用该包。

2
颤抖 - 我明白你为什么称它为黑客技巧了 - 请 - 不要在生产中使用。 - Tony Suffolk 66
12
这很有创意。邪恶的创意。 - Régis B.
3
为了帮助理解这是如何工作的,请访问https://dev59.com/LnA85IYBdhLWcg3wHvs0 - Jesse Vogt
1
@Eric,如果只有一个键:menu = ordereddict["about" : "about"],它不起作用;但是似乎很容易修复。 - TatianaP
显示剩余3条评论

40

要实现你所问的功能,你需要操作文件的语法树。我认为这样做不可取,但是我忍不住想尝试一下。所以我们开始吧。

首先,我们创建一个模块,并在其中定义一个函数my_execfile(),该函数的工作方式类似于内置的execfile()函数,但它会将所有字典显示替换为显式调用dict()构造函数的形式,例如:dict([(3, 4), ('a', 2)]),而不是像这样:{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()构造函数。您需要向DictDisplayTransformer类添加一个visit_DictComp()方法。鉴于上面的示例代码,这应该很容易实现。

再次提醒,我不建议这种搞乱语言语义的行为。您看过ConfigParser模块了吗?


3
好的,我会使用ConfigParser……不过你的解决方案很有启发性。非常感谢你。 - fdb
4
在你考虑更改语言语义之前,请思考“显式优于隐式”的原则。如果你试图覆盖'{}'或者隐藏它,以避免输入'OrderedDict',那么你最终会让你的代码变得更加难以阅读,不仅对他人而且对你自己在半年后也是如此。只需输入'OrderedDict'即可,这是可以理解的,并且可以实现你想要的功能,虽然需要打字更多,但可读性更高。 - Tony Suffolk 66

13

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())

6
我的问题不是OrderedDict是否是“标准Python语法”,而是是否可能覆盖{...}表示法。 - fdb
3
在Python中,{}创建一个dict对象,根据定义它是无序的。当然,你可以定义自己的语言,使用{}表示有序字典。你甚至可以编写一个小包装器将你的新语言转换为Python。这是你真正想要的吗? - Sven Marnach
2
@SvenMarnach:是的!但是我希望通过简单地用OrderedDict覆盖dict构造函数(dict = OrderedDict)来实现。 - fdb
2
@fdb:只有在通过调用dict()创建字典时才有效。 - Daenyth
sorted(dictname.items()) 会按照键的字典顺序升序排列项目。而 OrderedDict 的排序是按照插入顺序,而不是按照键的字典顺序。你可能在想 SortedDict(目前不存在)。 - PaulMcG
1
在你考虑改变编程语言的语义之前,请先思考“显式优于隐式”的原则——如果你试图覆盖'{}'或隐藏它以避免输入'OrderedDict',那么最终会使你的代码对其他人阅读更加困难,甚至对自己6个月后的阅读也是如此。只需输入'OrderedDict'即可,这是被理解的,并且可以实现你想要的功能——虽然需要更多的输入,但可以提高代码的可读性。 - Tony Suffolk 66

6

1
也许这与 https://docs.python.org/3/whatsnew/3.6.html#new-dict-implementation 所说的有关:“这种新实现中保持顺序的方面被认为是一种实现细节,不应该依赖于它”。尽管如此,我仍然觉得这些信息很有趣,所以给你点个赞! - Samuel
2
更新:插入顺序的保留现在已经成为3.7的标准,可以依赖它。 - Nick Sweeting

5

2
截至2016/12,Pypy实现将成为标准的Python dict实现,预测得非常好! - Nick Sweeting

5
你所要求的是不可能的,但如果一个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)])

9
"Impossible"这个词可能有些过于绝对了,可以看看我的回答。 - Sven Marnach
2
@Sven:是的,我非常喜欢你的回答! :) 不过,我想我会让我的措辞保持不变。请调整你在这种情况下对“不可能”的理解,以符合现实 ;) - Magnus Hoff
1
自Python 3.1以来,json.loadsjson.load也已更新,支持object_pairs_hook https://docs.python.org/3.4/library/json.html#json.load - Alex Bitek

0
如果你正在寻找一种简单易用的初始化语法,考虑创建一个OrderedDict的子类,并为其添加更新字典的操作符,例如:
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), ('key','value')])


使用切片语法的另一个可能的语法糖示例:

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"]

注意:这两个方法都极大地违反了其操作符的预期。__add____getitem__都旨在是非变异的,而切片支持应该是索引支持的一种聚合形式,而不是完全不相关的行为。违反这些预期就是在寻求可维护性的噩梦。slice技巧更好地用于实现接受的答案中给出的结果,其中它是一个工厂对象,可以创建一个普通的OrderedDict,而不是具有持续奇怪行为的OrderedDict替代品。 - ShadowRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接