Search code examples
pythonmetaprogrammingcode-injectionintrospection

Creating functions dynamically in Python -- f(2) cannot resolve (f1)


I am trying to dynamically create 2 functions defined within a string. Code:

def  main():

    fns = '''
    def plus_one(x):
    return x + 1


    def plus_two(x):
    return plus_one(x) + 1

    '''
    exec(fns)

    result = eval('plus_two(11)')

    print(result)

if __name__ == '__main__':
    main()

Saving this code to a file called dyn_code.py and running it gives me the following error:

python dyn_code.py  
Traceback (most recent call last):   
File "dyn_code.py", line 19, in <module>
main()   
File "dyn_code.py", line 14, in main   
result = eval('plus_two(11)')
File "<string>", line 1, in <module>   
File "<string>", line 7, in plus_two 
NameError: name 'plus_one' is not defined

Problem here is that plus_one cannot be resolved inside plus_two.

plus_one on its own is fine here and can be called with the correct result.

Can anybody please give me an idea on how to inject code like this into the local namespace? Specifically, I want to create 2 functions, with one referring to the other.

I have intentionally used the most open form of both exec and eval, I do know how to restrict them, etc. I have also verified that after the call to exec both functions are present in the local namespace.

What makes this more frustrating is that the code works fine in an interpreter session! That is, after injecting these 2 functions into the interpreter namespace via exec, plus_two runs without any issues.

Ideally, I would like to avoid a function-in-function scenario i.e.

def plus_two(x):
    def plus_one(x):
        return x + 1

    return plus_one(x) + 1

This technique actually works but I want 2 explicitly named and standalone functions.


Solution

  • indentations of your function in fns matters! and you have to pass globals() optional argument for mapping!

    def  main():
        fns = '''def plus_one(x):
        return x + 1
    
    def plus_two(x):
        return plus_one(x) + 1
        '''
        exec(fns,globals())
        result = eval('plus_two(11)')
    
        print(result)
    
    if __name__ == '__main__':
        main()
    

    Output:

    13
    

    Hope it helps!