views:

70

answers:

2

(Simplified from an excessively verbose question I posted earlier!)

Given a Python string containing valid Python code that contains a "yield" statement, how can I construct a generator that exec's that string?

For example, given the string:

code_string = """for x in range(0, 10):
    yield x
"""

I want to construct a generator f that executes code_string such that (in this particular example):

assert(list(f()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

Note that code_string is arbitrary, so this assertion is only valid for the example above. code_string can contain any valid python code that contains a yield statement.

Thanks!

Edit:

The first solution I thought of was to just munge "def f():" into the string and indent each line programmatically. However, that fails if code_string uses different indentation. I was hoping there were some little-known functools kung-fu that can construct a function from a blob of text.

Edit2:

I also tried an exec inside a function like so:

code = "for x in range(0, 10): yield x"
def f():
    exec code in globals(), locals()

This results in "SyntaxError: 'yield' outside function"

Solved: I stand corrected, indentation is relative, so this works:

code_string = """for x in range(0, 10):
    yield x
"""

exec "def f():\n" + [(" " + line) for line in code_string.split('\n')]) + "\n"
assert list(f()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+1  A: 

Try:

exec( """def f():
        for x in range(0, 10):
               yield x
""" )


for x in f():
    print x
brunson
Thanks, that works fine if the code passed in uses 4-space indents. However, valid Python can use 2-space indents, too. I'm looking for a solution that works with arbitrary values for code_string.
markv
I just thought of this... it's pretty easy to detect the indentation with a regular expression. max(min([indentations]), 4) will give a correct value.
markv
+1  A: 

What do you care what indentation is used within the function? Indentation is relative and does not have to be consistent. Tack "def f():\n" on the front, add a single space to each line in your function and exec it, and you're done. This works just as well as your answer:

exec "def f():\n " + " \n".join(code_string.splitlines()) + "\n" 

(Sorry to hear your pyparsing solution was too complicated. It sounds like you were trying to recreate a full Python grammar just to add a feature or two to the language. In this case, instead of parsing the whole thing using parseString, you can just add some specialized syntax, define a pyparsing expression for just that syntax, write a parse action that creates Python code from it, and use transformString to search and replace the special syntax with the corresponding Python behavior. Then you can exec or compile the whole transformed module, with just a little bit of pyparsing.)

Paul McGuire
So it is! I was under the (mistaken) impression that indentation had to be consistent throughout a compilation module. thanks.
markv