覆盖 {...} 表达式以返回 OrderedDict() 而非 dict()?

43 投票
7 回答
19469 浏览
提问于 2025-04-17 04:54

更新:从Python 3.7开始,字典会保留插入顺序。

我想把一个.py文件当作配置文件来用。

所以我可以用{...}这种写法来创建一个字典,用字符串作为键,但在标准的Python字典中,定义的顺序会丢失。

我的问题是:有没有办法重写{...}这种写法,让它返回一个OrderedDict()而不是dict()

我原本希望只要把字典的构造函数重写为OrderedDict(dict = OrderedDict)就能实现,但结果并没有成功。

例如:

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

print dictname.items()

输出:

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

7 个回答

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

要实现你想要的效果,你需要对文件的语法树进行一些调整。我觉得这样做并不太好,但我还是忍不住想试试。所以我们开始吧。

首先,我们创建一个模块,里面有一个函数 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() 构造函数的生成器表达式。你需要在 DictDisplayTransformer 类中添加一个 visit_DictComp() 方法。根据上面的示例代码,这应该很简单。

再次强调,我并不推荐这样去搞语言的语义。你有没有看过 ConfigParser 模块?

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 的包,它提供了更全面的实现 - 建议使用那个包

撰写回答