2

I want to create a Python script that takes in a string representation of a dictionary and outputs a list of tuples representing the items of the dictionary. The rub is, I want it to take in variables that have not been defined. An example illustrates this best:

Input: {'test': test}

Output: [('test': test)]

I've created some code to read in the dictionary and define the variables that haven't yet been defined, but when I eval() the result, it substitutes in the actual value of the variable rather than the variable name.

Here is the code:

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

I was hoping, for the above defined input value, that the dict it printed out would match the input string perfectly, but instead it has substituted in the value of 0 for test. Thus the script prints out:

{'test': 0}

I have tried ast.literal_eval, but it throws a ValueError: malformed string for any variable name in the literal, even if it's been defined.

Thus, my question is: Is there a way to convert the input dictionary without losing the variable names?

5 Answers 5

2

I would use the ast module, but not literal_eval. Walk the tree, when you find a variable reference, just insert the name of the variable into the output.

Sign up to request clarification or add additional context in comments.

2 Comments

According to the definition of ast.walk (and my own experimentation), the nodes are given back in no particular order, so I cannot output a reliable representation of the input string with it.
ast.walk isn't what you want. You need to visit each node in the tree. See stackoverflow.com/questions/1515357/… for an example.
2

You can trick eval into giving you what you want:

class Reflector(object):
    def __getitem__(self, name):
        return name

s = "{'test':test}"

print eval(s, globals(), Reflector())

produces:

{'test': 'test'}

The usual caveats about the dangers of eval hold though: if you have any doubts about the source of these strings, you are opening yourself up for hacking.

2 Comments

I have no doubts about the source.
This is interesting, but only gets me to the point of having a string with the name of the variable instead of the variable.
0

I think the problem you are having is that you want to evaluate the name of a variable instead of the value of that variable. In particular, {'test':test} would never be the output of printing a dictionary because test is not a value in python, it is possibly the name of a variable. The closest you can get is assigning

locals()[defname.group(1)] = defname.group(1)

to get

{'test':'test'}

Comments

0

I think a reasonable workaround is to pass eval something that preserves undefined variables in a useful way. You can pass any dict subclass to eval, and you can override the __missing__ method of dict to return an 'undefined variable' object:

>>> 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')}

Comments

0

I solved this problem by substituting in unique strings for the names of the variables. This produces the desired result:

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

Input: {"test": test}

Output: [('test', test)]

Input: {"test": [{"test": test}, {word: "word"}]}

Output: [('test', [{'test': test}, {word: 'word'}])] (note that this is desirable, I don't want the inner dictionary zipped).

The minor downside is that I can never have whatever substitution string I elect to use anywhere in the input string, but I am reasonably certain this will not be an issue for what I wish to use this script for.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.