runDOSrun
runDOSrun

Reputation: 10995

Convert tuple-strings to tuple of strings

My Input is:

input = ['(var1, )', '(var2,var3)']

Expected Output is:

output = [('var1', ), ('var2','var3')]

Iterating over input and using eval/literal_eval on the tuple-strings is not possible:

>>> eval('(var1, )')
>>> NameError: name 'var1' is not defined

How can I convert an item such as '(var1, )' to a tuple where the inner objects are treated as strings instead of variables?

Is there a simpler way than writing a parser or using regex?

Upvotes: 7

Views: 750

Answers (3)

Will
Will

Reputation: 24699

Try this:

tuples = [tuple(filter(None, t.strip('()').strip().split(','))) for t in input]

For example:

In [16]: tuples = [tuple(filter(None, t.strip('()').strip().split(','))) for t in input]

In [17]: tuples
Out[17]: [('var1',), ('var2', 'var3')]

We're iterating through our list of tuple strings, and for each one, removing the ()s, then splitting our string into a list by the ,, and then converting our list back into a tuple. We use filter() to remove empty elements.

Upvotes: 5

vaultah
vaultah

Reputation: 46533

For each occurrence of a variable, eval searches the symbol table for the name of the variable. It's possible to provide a custom mapping that will return the key name for every missing key:

class FakeNamespace(dict):
    def __missing__(self, key):
        return key

Example:

In [38]: eval('(var1,)', FakeNamespace())
Out[38]: ('var1',)

In [39]: eval('(var2, var3)', FakeNamespace())
Out[39]: ('var2', 'var3')

Note: eval copies current globals to the submitted globals dictionary, if it doesn't have __builtins__. That means that the expression will have access to built-in functions, exceptions and constants, as well as variables in your namespace. You can try to solve this by passing FakeNamespace(__builtins__=<None or some other value>) instead of just FakeNamespace(), but it won't make eval 100% safe (Python eval: is it still dangerous if I disable builtins and attribute access?)

Upvotes: 12

timgeb
timgeb

Reputation: 78700

I like vaultah's solution. Here's another one with ast.literal_eval and re if eval is not an option:

>>> import re
>>> from ast import literal_eval
>>> [literal_eval(re.sub('(?<=\(|,)(\w+)(?=\)|,)', r'"\1"', x)) for x in input]
[('var1',), ('var2', 'var3')]

Upvotes: 4

Related Questions