Reputation: 129
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.
Upvotes: 4
Views: 301
Reputation: 251146
Why your current code fails?
self
is a free variable to callback
and if you read locals()
's documentation you will find:
Free variables are returned by
locals()
when it is called in function blocks, but not in class blocks.
And now from exec()
's documentation:
If
exec
gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.
So, as we are passing two different objects to exec()
the locals()
dictionary is actually empty for callback()
as it cannot access free variables anymore, hence the solution suggested by @mgilson that passes merged version of locals()
and globals()
should do it for you.
Upvotes: 3
Reputation: 310227
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)
Upvotes: 4