繁体   English   中英

如何在literal_eval之前用Python AST中的Dict替换OrderedDict?

[英]How can I replace OrderedDict with dict in a Python AST before literal_eval?

我有一个包含Python代码的字符串,如果它只用OrderedDict实例替换为{} ,我可以用literal_eval作为Python进行评估。

我正在尝试使用ast.parseast.NodeTransformer进行替换,但是当我使用nodetype == 'Name' and node.id == 'OrderedDict'捕获节点时,我找不到列表节点对象中的参数,以便我可以用Dict节点替换它。

这甚至是正确的方法吗?

一些代码:

from ast import NodeTransformer, parse

py_str = "[OrderedDict([('a', 1)])]"

class Transformer(NodeTransformer):
    def generic_visit(self, node):
        nodetype = type(node).__name__

        if nodetype == 'Name' and node.id == 'OrderedDict':
            pass # ???

        return NodeTransformer.generic_visit(self, node)

t = Transformer()

tree = parse(py_str)

t.visit(tree)

我们的想法是替换所有OrderedDict节点,表示为具有特定属性的ast.Call (可以从下面的ordered_dict_conditions中看到),其中ast.Dict节点的key / value参数是从ast.Call参数中提取的。

import ast


class Transformer(ast.NodeTransformer):
    def generic_visit(self, node):
        # Need to call super() in any case to visit child nodes of the current one.
        super().generic_visit(node)
        ordered_dict_conditions = (
            isinstance(node, ast.Call)
            and isinstance(node.func, ast.Name)
            and node.func.id == 'OrderedDict'
            and len(node.args) == 1
            and isinstance(node.args[0], ast.List)
        )
        if ordered_dict_conditions:
            return ast.Dict(
                [x.elts[0] for x in node.args[0].elts],
                [x.elts[1] for x in node.args[0].elts]
            )
        return node


def transform_eval(py_str):
    return ast.literal_eval(Transformer().visit(ast.parse(py_str, mode='eval')).body)


print(transform_eval("[OrderedDict([('a', 1)]), {'k': 'v'}]"))  # [{'a': 1}, {'k': 'v'}]
print(transform_eval("OrderedDict([('a', OrderedDict([('b', 1)]))])"))  # {'a': {'b': 1}}

笔记

因为我们想首先替换最里面的节点,所以我们在函数的开头调用super()

每当遇到OrderedDict节点时,都会使用以下内容:

  • node.args是一个包含OrderedDict(...)调用参数的列表。
  • 此调用具有单个参数,即包含键值对作为元组的列表,可由node.args[0]ast.List )和node.args[0].elts是包含在list中的元组。
  • 所以node.args[0].elts[i]是不同的ast.Tuple s( for i in range(len(node.args[0].elts)) ),其元素可以通过.elts属性再次访问。
  • 最后, node.args[0].elts[i].elts[0]是键, node.args[0].elts[i].elts[1]是在OrderedDict调用中使用的值。

然后使用后面的键和值来创建一个新的ast.Dict实例,然后用它来替换当前节点(即ast.Call )。

您可以使用ast.NodeVisitor类来观察OrderedDict树,以便从遇到的节点手动构建{}树,使用空字典中的解析节点作为基础。

import ast
from collections import deque


class Builder(ast.NodeVisitor):
    def __init__(self):
        super().__init__()
        self._tree = ast.parse('[{}]')
        self._list_node = self._tree.body[0].value
        self._dict_node = self._list_node.elts[0]
        self._new_item = False

    def visit_Tuple(self, node):
        self._new_item = True
        self.generic_visit(node)

    def visit_Str(self, node):
        if self._new_item:
            self._dict_node.keys.append(node)
        self.generic_visit(node)

    def visit_Num(self, node):
        if self._new_item:
            self._dict_node.values.append(node)
            self._new_item = False
        self.generic_visit(node)

    def literal_eval(self):
        return ast.literal_eval(self._list_node)


builder = Builder()
builder.visit(ast.parse("[OrderedDict([('a', 1)])]"))
print(builder.literal_eval())

请注意,这仅适用于示例的简单结构,该结构使用str作为键, int作为值。 但是,应该以类似的方式扩展更复杂的结构。

而不是使用ast来解析和转换表达式,您也可以使用正则表达式来执行此操作。 例如:

>>> re.sub(
...     r"OrderedDict\(\[((\(('[a-z]+'), (\d+)\)),?\s*)+\]\)",
...     r'{\3: \4}',
...     "[OrderedDict([('a', 1)])]"
... )
"[{'a': 1}]"

上面的表达式基于OP的示例字符串,并将单引号字符串视为键,将正整数视为值,但当然可以将其扩展为更复杂的情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM