Search code examples
pythonpython-exec

Closure lost during callback defined in exec()


It's my third day using Python, so forgive newbie mistakes. So here's my working code. person.test() registers a callback with the boss, the boss calls the callback, everything works fine.

class Boss:
  def registerCallback(self,cb):
    self.cb = cb
  def doCallback(self):
    self.cb()

class Person:
  def woot(self,data):
    print("Woot! ",data)

  def test(self,boss,data):
    def callback ():
      self.woot(data)
    boss.registerCallback(callback)    

boss = Boss()
person = Person()
person.test(boss,1)
boss.doCallback()

However, if I change move the callback into an exec(), the closure is lost. The callback runs, but self and data are unknown so the call to self.woot(data) fails.

class Boss:
  def registerCallback(self,cb):
    self.cb = cb
  def doCallback(self):
    self.cb()

class Person:
  def woot(self,data):
    print("Woot! ",data)

  def test(self,boss,data):
    x = "def callback():\n  self.woot(data)\nboss.registerCallback(callback)"
    exec(x,globals(),locals())

boss = Boss()
person = Person()
person.test(boss,1)
boss.doCallback()

I tried to compile() too, no luck. Any thoughts? I really don't want to manually carry a copy of self/data through the boss and back, because my real-life code is much more convoluted. I really need a way to maintain the closure.


Solution

  • If you only pass locals (as the global data for the function), then things more or less work:

    class Person:
      def woot(self,data):
        print("Woot! ",data)
    
      def test(self,boss,data):
        x = "def callback():\n  self.woot(data)\nboss.registerCallback(callback)"
        exec(x, locals())
    

    of course, if you need the globals as well, you can pack them together:

    def test(self, boss, data):
      namespace = globals().copy()
      local_copy = locals().copy()
      namespace.update(local_copy)
      x = 'def foo(): pass'
      exec(x, namespace)