@JonClements 的解决方案既漂亮又简单——但是,正如他所指出的,它并不是那么健壮,因为你依赖于字典显示的每个元素都会对自身进行评估这一事实——而且你已经得到了一些任意代码,其中第一个有效的 dict 文字是您唯一关心的。
一个相关的想法是使用 ast.NodeTransformer 将 dict 文字 AST 转换为 OrderedDict 构造函数 AST,然后只需 eval 即可。
优点:
- 一旦您让它适用于琐碎的案例,它就会自动适用于更复杂的案例。
- 将它从解析单个 dict 文字扩展到转换整个模块中的所有 dict 文字(然后您可以将其作为导入钩子的一部分安装)是微不足道的。
- 您将进一步了解 Python AST 的工作原理。
缺点:
- 要编写更多(也更丑陋)的代码,使其适用于琐碎的案例。
- 由于您不手动解析元素,因此添加限制并不那么容易,例如,安全地处理潜在的恶意或无能输入(例如,通过在每个元素上使用
literal_eval)。
- 您必须详细了解 Python AST 的工作原理。
但是,有必要退后一步,询问您是否真的想编写和使用所有这些代码。使用MacroPy 之类的东西可能会更快乐,它可以自动完成很多在这里完成的笨重工作,以及很多我不在这里做的事情(比如安装导入挂钩) ,让您只专注于您感兴趣的转换部分。 (实际上,我认为 MacroPy 甚至带有一个 odict 文字作为其内置示例之一……)
反正变压器是这样的:
class DictToOrdered(ast.NodeTransformer):
def visit_Dict(self, node):
return ast.fix_missing_locations(ast.copy_location(
ast.Call(
func=ast.Attribute(
value=ast.Name(id='collections', ctx=ast.Load()),
attr='OrderedDict',
ctx=ast.Load()),
args=[ast.Tuple(elts=
[ast.Tuple(elts=list(pair), ctx=ast.Load())
for pair in zip(node.keys, node.values)],
ctx=ast.Load())],
keywords=[],
starargs=None,
kwargs=None),
node))
这比平时有点难看,因为 dict 字面量不必有上下文(因为它们不能用作赋值目标),但元组有(因为它们可以),所以我们不能只按照我们处理行号的方式复制上下文。
使用它:
def parse_dict_as_odict(src):
import collections
parsed = ast.parse(src, '<dynamic>', 'eval')
transformed = DictToOrdered().visit(parsed)
compiled = compile(transformed, '<dynamic>', 'eval')
return eval(compiled)
假设您想一次只计算一个表达式,并且您想在当前的全局/本地环境中执行此操作,并且您不介意将collections 模块插入该环境;如果您查看 compile、ast.parse 和 eval 的文档,那么如何更改这些假设应该很明显。
所以:
>>> src = '''
... {
... 'key1': 'value1',
... 'key2': 'value2',
... 'key3': 'value3',
... }
... '''
>>> parse_dict_as_odict(src)
OrderedDict([('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')])
如果您想了解更多信息,而无需亲自深入研究源代码,Green Tree Snakes 是了解 Python 的 AST 及其 ast 模块的绝佳资源,我希望几年前就已经编写了该模块。 :)