Python ссылка на обратный вызов в словаре - PullRequest
6 голосов
/ 13 октября 2011

У меня есть класс, который определяет набор функций обратного вызова (здесь показано как cb1 и cb2). У меня есть карта, которую я хочу назвать после некоторого события.

class Foo:
    cb1 = None
    cb2 = None

    def test(self, input):
        for (name, callback) in map:
            if name == input:
                if callback: callback()
                ...

    map = {'one':cb1, 'two':cb2}

def mycallback():
    print "mycallback()"

f = Foo()
f.cb1 = mycallback  # Register our callback
f.test('one')     # Nothing happens

Можете ли вы определить проблему?

Что происходит, когда инициализируется класс, значения из cb1 и cb2 (которые оба None) копируются в карту. Таким образом, даже после того, как пользователь «зарегистрировал» обратный вызов (присваивая cb1), значение на карте по-прежнему равно None и ничего не вызывается.

Поскольку в Python нет такой вещи, как «по ссылке», как мне это исправить?

Ответы [ 5 ]

12 голосов
/ 13 октября 2011

Почему бы не сделать так, чтобы ваш класс явно обрабатывал регистрацию?

import collections

class Foo(object):
    handlers = None

    def __init__(self):
        self.handlers = collections.defaultdict(set)

    def register(self, event, callback):
        self.handlers[event].add(callback)

    def fire(self, event, **kwargs):
        for handler in self.handlers.get(event, []):
            handler(**kwargs)

foo = Foo()
foo.register('one', mycallback)
foo.fire('one')
1 голос
/ 13 октября 2011

Добавить функцию регистрации. В классе Foo:

def register(self, name, cb): self.map[name] = cb

и вместо:

f.cb1 = mycallback

использование:

f.register('one', mycallback)  
0 голосов
/ 13 октября 2011

Почему вам нужно установить другую переменную для настройки обратного вызова, чем та, которая фактически используется для ее выполнения? Если вы используете ту же переменную, проблема исчезает.

С некоторым синтаксическим сахаром это может выглядеть так:

class CallbackMap(object):
    pass

class Foo(object):
    callbacks = CallbackMap()

    def test(self, input):
        callback = getattr(Foo.callbacks, input)
        if callback: callback()

# setup defaults
Foo.callbacks.one = None
Foo.callbacks.two = some_default_callback

# customize
def mycallback():
    print "mycallback()"

f = Foo()
Foo.callbacks.one = mycallback  # Register our callback
f.test('one') # works
0 голосов
/ 13 октября 2011

С дескриптором делегата и небольшим обманом атрибутов.

class Delegate(object):
  def __get__(self, instance, owner):
    return instance._cbs.get(self, lambda x: None)

  def __set__(self, instance, value):
    if not hasattr(instance, '_cbs'):
      instance._cbs = {}
    instance._cbs[self] = value

  def __delete__(self, instance):
    if not hasattr(instance, '_cbs'):
      instance._cbs = {}
    instance._cbs[self] = lambda x: None

  def __hash__(self):
    return id(self)

class C(object):
  cb1 = Delegate()
  map = {'one': 'cb1'}

  def test(self, cb):
    getattr(self, self.map[cb])()

def foo():
  print 'bar!'

c = C()
c.cb1 = foo
c.test('one')
0 голосов
/ 13 октября 2011

Наоборот, все это "по ссылке" в Python.Но вы копируете ссылку на None в свой словарь, и изменение исходного слота ничего не делает для этой ссылки.Если вы хотите сохранить дополнительный уровень косвенности, то самый простой способ - хранить строки.Если все ваши обратные вызовы являются атрибутами этого класса, избавьтесь от map и просто сохраните список имен атрибутов обратного вызова.callback_names = ['cb1', 'cb2'], а затем используйте getattr(self, callback_name)() для вызова обратного вызова.Если у вас должна быть карта, то вы можете сделать map = {'one': 'cb1', 'two': 'cb2'}.

Вы также можете сделать что-то необычное со свойствами, но это кажется излишне сложным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...