【问题标题】:How to eval a dict py file Ordered如何评估 dict py 文件 Ordered
【发布时间】:2014-10-27 08:23:36
【问题描述】:

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

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

然后我读取这个文件并转换字典:

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

但是这个“dict_from_file”的key1、key2、key3的顺序不一样。

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

【问题讨论】:

  • Eval 会将数据视为无序的字典,它会打乱密钥。转换为有序字典将毫无意义。您可能需要解析数据并逐个令牌提供数据。
  • @Mr.Polywhirl:虽然这个问题是相关的,但我认为它所描述的答案在这里并不合适。此问题中的文件包含 Python 文字,而不是 JSON。确实,示例数据几乎是有效的JSON(只需将单引号改为双引号并去掉最后一个逗号),但我不知道真实数据是否如此。
  • 在我看到 Jon Clements 的回答之前,我会推荐使用像 pyparsing 这样的解析库。现在我认为他的想法可能更好......但如果你以前从未有过的话,它仍然值得看看pyparsing 和玩它。 (或者,如果您对解析器有更多了解,请在 PyPI 中搜索其他可能更熟悉的解析器和 pgen 库。)

标签: python


【解决方案1】:

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

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

没有直接的解决方法。如果你使用eval 解析一个包含字典文字的文件,它会给你一个普通的dict

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

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

[
    ('key1', 'value1'),
    ('key2', 'value2'),
    ('key3', 'value3'),
]

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

OrderedDict(
    key1='value1',
    key2='value2',
    key3='value3',
)

唉,如果您今天尝试此操作,您将遇到与当前代码相同的问题(关键字参数被打包到常规 dict 中,在 OrderedDict 代码查看它们之前丢弃它们的顺序) . OrderedDict 构造函数的关键字参数并不是非常有用。

【讨论】:

  • 您好@Blckknght,感谢您的回复。但这个文件是其他应用程序的。那我就没有权限修改这个文件了。
  • 即使可能,最后提供的解决方案也不起作用,因为OrderedDict 采用**kwargs dict,这意味着它将以任意顺序获取这些键和值,让您正确你从哪里开始。
  • @abarnert:这正是我在回答中所说的。如果 PEP 468 被颁布,它可能会成为一种选择,因为它建议将所有 **kwargs 字典设为OrderedDicts,但今天它不会起作用。
【解决方案2】:

您可以使用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')])

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

【讨论】:

  • 聪明的解决方案!尽管可能有一种方法可以利用更多的 ast 机制使其更加健壮,但可以通过使用 NodeTransformer 将 dict 文字转换为元组的 OrderedDict 构造函数。让我看看我能不能让它发挥作用……但即使我能做到,作为起点,你的可能仍然更具可读性和更容易理解。
  • @abarnert 我没有时间这样做 - 就像我说的那样 - 这是一个起点......希望比任何事情都更鼓舞人心 :) 但如果你有时间并且可以获得@ 987654326@ 方法有效(我认为这听起来很可行)然后请分享 - 我很想看到它。
【解决方案3】:

@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 模块插入该环境;如果您查看 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 模块的绝佳资源,我希望几年前就已经编写了该模块。 :)

【讨论】:

  • 我曾担心会是这样 :) 但绝对是 +1 - 很棒的工作
  • 附带说明:那里是否有“缺点:”?
  • @JonClements:是的,谢谢;列表的后半部分是缺点。除非您认为更丑更冗长是专业人士。 :) 我会编辑它。
  • @abarnert :) 好吧,我只能说,如果我是问题的 OP,我会毫不犹豫地接受这个经过充分研究、解释和详细的答案。在“简单并符合规范”(我们通常知道这不是实际规范:P)和这个更强大的规范之间折腾......我会选择这个。
  • @JonClements:老实说,我可能会选择一个仅链接的答案,说“使用 MacroPy”而不是任何一个,但这不适合 Stack Overflow。 :)
猜你喜欢
  • 2018-10-07
  • 2021-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
  • 1970-01-01
相关资源
最近更新 更多