Как я могу заменить OrderedDict на dict в Python AST перед буквальным eval?
У меня есть строка с кодом Python, которую я мог бы оценить как Python с literal_eval
, если бы в ней были только экземпляры OrderedDict
, замененные на {}
.
Я пытаюсь использовать 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)
3 ответа:
Идея состоит в том, чтобы заменить все
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
, чтобы построить дерево{}
вручную из найденных узлов, используя разбираемые узлы из пустого 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
в качестве значений. Однако расширение более сложных структур должно быть возможно аналогичным образом.
Вместо того, чтобы использовать
ast
для разбора и преобразования выражения, вы можете также использовать регулярное выражение для этого. Например:Приведенное выше выражение основано на примере строки OP и рассматривает одиночные строки в кавычках как ключи и положительные целые числа как значения, но, конечно, оно может быть распространено на более сложные случаи.>>> re.sub( ... r"OrderedDict\(\[((\(('[a-z]+'), (\d+)\)),?\s*)+\]\)", ... r'{\3: \4}', ... "[OrderedDict([('a', 1)])]" ... ) "[{'a': 1}]"