覆盖 {...} 表达式以返回 OrderedDict() 而非 dict()?
更新:从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 个回答
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())
要实现你想要的效果,你需要对文件的语法树进行一些调整。我觉得这样做并不太好,但我还是忍不住想试试。所以我们开始吧。
首先,我们创建一个模块,里面有一个函数 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
模块?
这里有个小技巧,几乎能让你得到你想要的语法:
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
的包,它提供了更全面的实现 - 建议使用那个包