Я хочу создать скрипт Python, который принимает строковое представление словаря и выводит список кортежей, представляющих элементы словаря. Потребуется, я хочу, чтобы он принимал переменные, которые не были определены. Пример иллюстрирует это лучше всего:
Вход: {'test': test}
Выход: [('test': test)]
Я создал некоторый код для чтения в словаре и определяю переменные, которые еще не определены, но когда я получаю результат eval()
, он заменяет фактическое значение переменной, а не имя переменной.
Вот код:
import sys
import re
if __name__ == "__main__":
instr = sys.argv[1]
success = False
while not success:
try:
indict = eval(instr)
success = True
except NameError, e:
defname = re.search("name '([^\']*)' is not defined", str(e))
locals()[defname.group(1)] = 0
print indict
Я надеялся, что для указанного выше входного значения, что он напечатал, будет идеально соответствовать строке ввода, но вместо этого он заменит значение 0 для test
. Таким образом, скрипт печатает:
{'test': 0}
Я пробовал ast.literal_eval
, но он выбрасывает ValueError: malformed string
для любого имени переменной в литерале, даже если она была определена.
Таким образом, мой вопрос: есть ли способ конвертировать входной словарь без потери имен переменных?
Я решил эту проблему, заменив в уникальных строках имена переменных. Это дает желаемый результат:
import sys
import re
if __name__ == "__main__":
instr = sys.argv[1]
subs = []
success = False
while not success:
try:
indict = eval(instr)
success = True
except NameError, e:
defname = re.search("name '([^\']*)' is not defined", str(e)).group(1)
subs.append(defname)
locals()[defname] = '//substitute{%d}' % (len(subs) - 1)
outlist = indict.items()
outstr = str(outlist)
for i, sub in enumerate(subs):
outstr = outstr.replace("'//substitute{%d}'" % i, sub)
print outstr
Вход: {"test": test}
Выход: [('test', test)]
Вход: {"test": [{"test": test}, {word: "word"}]}
Вывод: [('test', [{'test': test}, {word: 'word'}])]
(обратите внимание, что это желательно, я не хочу, чтобы внутренний словарь застегивался).
Недостатком является то, что я никогда не могу использовать любую строку подстановки, которую я выбираю для использования в любом месте входной строки, но я уверен, что это не будет проблемой для того, для чего я хочу использовать этот скрипт.
Я бы использовал модуль ast
, но не literal_eval
. Пройдите дерево, когда вы найдете ссылку на переменную, просто вставьте имя переменной в вывод.
Вы можете обмануть eval
чтобы дать вам то, что вы хотите:
class Reflector(object):
def __getitem__(self, name):
return name
s = "{'test':test}"
print eval(s, globals(), Reflector())
производит:
{'test': 'test'}
Обычные предостережения об опасностях eval
хотя: если у вас есть какие-либо сомнения относительно источника этих строк, вы открываете себя для взлома.
Я думаю, что разумным обходным путем является переход eval
который сохраняет неопределенные переменные полезным способом. Вы можете передать любой подклассы dict
для eval, и вы можете переопределить метод __missing__
dict, чтобы вернуть объект неопределенной переменной:
>>> class Var(object):
... def __init__(self, name):
... self.name = name
... def __repr__(self):
... return "Var(%r)" % (self.name,)
...
>>> class UndefSubst(dict):
... def __missing__(self, key):
... return Var(key)
...
>>> foo = 'bar'
>>> eval("{'baz': quux, 1: foo}", UndefSubst(locals()))
{1: 'bar', 'baz': Var('quux')}
Я думаю, что проблема, с которой вы сталкиваетесь, заключается в том, что вы хотите оценить имя переменной вместо значения этой переменной. В частности, {'test':test}
никогда не будет результатом печати словаря, потому что test
не является значением в python, возможно, это имя переменной. Самое близкое, что вы можете получить, это присвоение
locals()[defname.group(1)] = defname.group(1)
получить
{'test':'test'}
ast.walk
(и моему собственному эксперименту), узлы возвращаются вast.walk
порядке, поэтому я не могу вывести с ним достоверное представление входной строки.ast.walk
не то, что вы хотите. Вам необходимо посетить каждый узел в дереве. См. Для примера stackoverflow.com/questions/1515357/…