Закрытие потеряно во время обратного вызова, определенного в exec()

Это мой третий день использования Python, так что простите ошибки новичка. Итак, вот мой рабочий код. person.test () регистрирует обратный вызов у ​​босса, босс вызывает обратный вызов, все работает нормально.

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()

Тем не менее, если я изменяю перемещение обратного вызова в exec(), закрытие теряется. Обратный вызов выполняется, но self и данные неизвестны, поэтому вызов self.woot(data) не выполняется.

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()

Я попытался скомпилировать () тоже, не повезло. Какие-нибудь мысли? Я действительно не хочу вручную переносить копию себя / данных через босса и обратно, потому что мой реальный код гораздо более запутанный. Мне действительно нужен способ сохранить закрытие.

2 ответа

Решение

Если вы только проходите locals (как глобальные данные для функции), то вещи более или менее работают:

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())

конечно, если вам нужны глобальные переменные, вы можете упаковать их вместе:

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

Почему ваш текущий код не работает?

self является свободной переменной для callback и если ты читаешь locals() Документацию вы найдете:

Свободные переменные возвращаются locals() когда он вызывается в функциональных блоках, но не в блоках классов.

А теперь из exec() Документация:

Если exec получает два отдельных объекта как глобальные и локальные, код будет выполняться так, как если бы он был встроен в определение класса.

Итак, когда мы передаем два разных объекта exec() locals() словарь на самом деле пуст для callback() поскольку он больше не может получить доступ к свободным переменным, следовательно, решение, предложенное @mgilson, передает объединенную версию locals() а также globals() должен сделать это для вас.

Другие вопросы по тегам