Search code examples
pythonexec

Pass 'self' into a function defined via python exec in a class method


I have the following piece of code:

class A:
    def __init__(self):
        self.name = "foo"
    def hello(self):
        exec("def func():\n print(self.name)")
        import pdb; pdb.set_trace()

aa = A()
aa.hello()

Running this code triggers the pdb shell. Then I observe that

-> import pdb; pdb.set_trace()
(Pdb) func
<function func at 0x750d187a6950>
(Pdb) func()
*** NameError: name 'self' is not defined

So, func is a known function. However, self is not defined.

How to fix this?

Update: To clarify, my intent is to define a function dynamically in a class method, such that the function's execution depends on properties of an instance of that class.

Update 2: To clarify, I wanted the debugger to act as a shell -- that is specific to my use case. (That's why I included pdb line in my code snippet);

The snippet here is just a simplified snippet of my use case, which involves dynamically creating a bunch of functions based on some user input. And the user can interact with those functions through a pdb session

Update 3: Thanks @Ammar. I have upvoted your answer. What about the case where the function execution depends on executing a method (not just a property)? As an example,

class A:
    def foo(self):
        print("running foo")
    def hello(self):
        exec("def func():\n self.foo()")
        import pdb; pdb.set_trace()

aa = A()
aa.hello()

Solution

  • In your function func() there are no parameters passed hence it throws error NameError: name 'self' is not defined.
    Try this:

    class A:
        def __init__(self):
            self.name = "foo"
        def hello(self):
            exec(f"def func(name):\n print(name)")
            import pdb; pdb.set_trace()
    
    aa = A()
    aa.hello()
    

    In the ternimal, pass self.name in func()

    -> import pdb; pdb.set_trace()
    (Pdb) func(self.name)
    foo
    (Pdb)
    

    Updated answer to the edited question

    from textwrap import dedent
    class A:
        def __init__(self):
            self.name = "foo"
        def hello(self):
            exec_code = dedent('''
            name = self.name
            def func():
                global name
                print(name)
            ''')
            exec(exec_code)
            import pdb; pdb.set_trace()
    
    aa = A()
    aa.hello()
    

    In the terminal:

    -> import pdb; pdb.set_trace()
    (Pdb) func()
    foo
    (Pdb)  
    

    Update 3 answer

    Same procedure, store self (instance) in a global variable and use that variable to call the instance's methods.
    Code:

    from textwrap import dedent
    class A:
        def foo(self):
            print("running foo")
        def hello(self):
            exec_code = dedent('''
            curr_obj = self
            def func():
                global curr_obj
                curr_obj.foo()
            ''')
            exec(exec_code)
            import pdb; pdb.set_trace()
    
    aa = A()
    aa.hello()
    

    Terminal:

    -> import pdb; pdb.set_trace()
    (Pdb) func()
    running foo
    (Pdb)