[英]How can I replace OrderedDict with dict in a Python AST before literal_eval?
我有一个包含Python代码的字符串,如果它只用OrderedDict
实例替换为{}
,我可以用literal_eval
作为Python进行评估。
我正在尝试使用ast.parse
和ast.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.