如何评估有序的dict py文件

时间:2014-09-02 23:47:36

标签: python

我有一个名为example_dict.py的文件

#This is a valid comment
{
    'key1': 'value1',
    'key2': 'value2',
    'key3': 'value3',
}

然后我读了这个文件并转换dict:

from collections import OrderedDict
with open("example_dict.py") as fp:
    dict_from_file = OrderedDict( eval( fp.read() ) )

但是这个" dict_from_file"没有相同的顺序key1,key2,key3。

我怎么能以同样的顺序得到这个字典。

3 个答案:

答案 0 :(得分:6)

您可以使用ast模块编写自定义解析器,作为启动器:

import ast
from collections import OrderedDict

with open('example_dict.py') as fin:
    parsed = ast.parse(fin.read())

first_dict = next(node for node in ast.walk(parsed) if isinstance(node, ast.Dict))
keys = (node.s for node in first_dict.keys)
vals = (node.s for node in first_dict.values)
od = OrderedDict(zip(keys, vals))
# OrderedDict([('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3')])

请注意,尽管这适用于您的示例数据 - 这需要更多工作才能使其更加健壮,但应该作为起点。

答案 1 :(得分:5)

@ JonClements'解决方案既美观又简单 - 但是,正如他所指出的那样,它并不那么强大,因为你依赖于字典显示的每个元素都会对自身进行评估这一事实 - 而且你需要这样做。我得到了一些任意代码,其中第一个有效的字典文字是你唯一关心的。

一个相关的想法是使用ast.NodeTransformer将dict文字AST转换为OrderedDict构造函数AST,然后只用eval

优点:

  • 一旦你让它适用于琐碎的案例,它会自动适用于更复杂的案例。
  • 将它从解析单个字典文字扩展到转换整个模块中的所有字典文字(然后可以将其作为导入挂钩的一部分安装)是微不足道的。
  • 您可以了解有关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))

这比平时稍微丑陋,因为字面文字不必具有上下文(因为它们不能用作赋值目标),但是元组(因为它们可以),所以我们不能像我们行号那样复制上下文。

使用它:

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模块插入到该表达式中环境;如果您查看compileast.parseeval的文档,那么应该明白如何更改任何这些假设。

所以:

>>> 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模块的理想资源。几年前。 :)

答案 2 :(得分:1)

Python词典没有任何固有的顺序。您可能已经知道这一点,因为您正在尝试将数据放入OrderedDict的实例中,这样可以保持其值的添加顺序。

但是,您遇到的问题是您的eval表达式首先生成普通的dict实例,并且只有在订单丢失后才会传递给{ {1}}。

没有直接解决这个问题。如果您使用OrderedDict来解析其中包含字典文字的文件,那么它将为您提供常规eval

但还有其他选择。您可以编写自己的解析代码,并创建值以直接放入dict,而无需先创建常规OrderedDict。这有点复杂,如果这是你要采用的方法,你应该选择更好的文件格式。

如果实际上您可以更改文件的内容,您可以简单地让dict调用创建一些其他数据结构,您可以传递给eval而不会丢失订购信息。 OrderedDict 2元组列表是一个不错的选择,不需要对代码进行其他更改:

(key,value)

请注意,在Python的某个未来版本中,函数调用中传递的关键字参数可能会被放入[ ('key1', 'value1'), ('key2', 'value2'), ('key3', 'value3'), ] 而不是OrderedDict(如PEP 468中所述)。如果发生这种情况,您可以将文件内容更改为以下内容,并直接从dict获取OrderedDict

eval

唉,如果您今天尝试这个,那么您将遇到当前代码所遇到的相同问题(关键字参数被打包到常规OrderedDict( key1='value1', key2='value2', key3='value3', ) 中,这会在dict代码之前丢弃它们的排序看看他们)。 OrderedDict构造函数的关键字参数并不十分有用。