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

时间:2018-07-09 19:51:32

标签: python abstract-syntax-tree

我有一个包含Python代码的字符串,如果它只用literal_eval替换了OrderedDict的实例,那么我可以将{}视为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)

3 个答案:

答案 0 :(得分:2)

该想法是将所有具有特定属性(可以从下面的OrderedDict看到的ast.Call表示为ordered_dict_conditions的节点替换为{{1} 1}} / 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.argsOrderedDict(...))可以访问,而node.args[0]是包装在{中的元组{1}}。
  • 因此ast.List是不同的node.args[0].eltslist),其元素可以通过node.args[0].elts[i]属性再次访问。
  • 最后,ast.Tuple是键,for i in range(len(node.args[0].elts))是在.elts调用中使用的值。

随后的键和值将用于创建一个新的node.args[0].elts[i].elts[0]实例,然后将其用于替换当前节点(即node.args[0].elts[i].elts[1])。

答案 1 :(得分:0)

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

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作为值。但是,应该以类似的方式扩展到更复杂的结构。

答案 2 :(得分:0)

除了使用ast来解析和转换表达式之外,您还可以使用正则表达式来执行此操作。例如:

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

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