2

In python3, if a function with recursive invoking is injected into exec() in a function, I got an error. For example, below code

def B(pys):
    exec(pys)

pys="""
def fibonacci(n):
    if n == 1 or n == 2:
        r = 1
    else:
        r = fibonacci(n - 1) + fibonacci(n - 2)
    return r

print(fibonacci(3))
"""
B(pys)

will raise NameError.

$ py -3.8 testrecursivefun.py
Traceback (most recent call last):
  File "testrecursivefun.py", line 14, in <module>
    B(pys)
  File "testrecursivefun.py", line 2, in B
    exec(pys)
  File "<string>", line 9, in <module>
  File "<string>", line 6, in fibonacci
NameError: name 'fibonacci' is not defined

If I run exec(pys) directly under the module, the exception disappeared.

The reason has been described in another question How does exec work with locals?. But I still don't know how I can figure out the recursive invoking in exec(). Because the function name is dynamic for me. I cannot add it to locals() to exec(). Who can help me figure it out.

ZMJ
  • 337
  • 2
  • 11

2 Answers2

1

For the sake of an answer, you can wrap your code in a function so the recursive function is in its local scope:

import textwrap

def B(pys):
    exec(pys, globals(), {})

pys="""
def fibonacci(n):
    if n == 1 or n == 2:
        r = 1
    else:
        r = fibonacci(n - 1) + fibonacci(n - 2)
    return r

print(fibonacci(11))
"""

def wrap(s):
    return "def foo():\n" \
           "{}\n" \
           "foo()".format(textwrap.indent(s, ' ' * 4))

B(wrap(pys))

Generally, reconsider using exec.

user2357112
  • 260,549
  • 28
  • 431
  • 505
zxzak
  • 8,985
  • 4
  • 27
  • 25
  • 1
    It's brilliant. Thank you so much. @zxzak. Do you know the reason. Why the second layer inner function can record the itself in local variable list, but the first layer can't? Is it a python's bug. Why did python3 developer close the bug report https://bugs.python.org/issue4831? – ZMJ Jul 11 '20 at 03:34
-1

I actually got interested in your question, so I started researching on this topic. Seems like the simple solution to your problem is to:

  1. First compile the string to code using compile function in python
  2. Then execute the compiled code using exec function

Here is the sample solution:

psy="""
def fibonacci(n):
    if n == 1 or n == 2:
        r = 1
    else:
        r = fibonacci(n - 1) + fibonacci(n - 2)
    return r

print(fibonacci(3))
"""

def B(psy):
    code = compile(psy, '<string>', 'exec')
    exec(code, globals())

B(psy)

Here compile takes three parameters:

First is the code in string format, Second is the filename hint which we used as we take string as code itself, and third can be one of 'exec', 'eval' and 'single'.

This link contains detail explanation of how you should use exec and eval in python. Do check them out for detail explanation.

PaxPrz
  • 1,778
  • 1
  • 13
  • 29
  • Did you try to run your code? It doesn't work. I tried the same method before I raise this question. – ZMJ Jul 11 '20 at 03:35
  • 1
    This executes the code in the global scope, introducing namespace pollution and clobbering issues. Also, the `compile` step is unnecessary. `exec` will automatically compile strings. `compile` is primarily useful if you want to save the bytecode and execute it repeatedly, or if you want to inspect or modify the bytecode. – user2357112 Jul 11 '20 at 04:57
  • Thank you for mentioning. I am sure, I need to go deep on this. Could you provide me any supporting links where I can study on detail :) – PaxPrz Jul 11 '20 at 05:10