Питон ссылку на callback в словарь

у меня есть класс, который задает набор функций обратного вызова (показано здесь как 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

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

что происходит, когда класс инициализируется,значения of cb1 и cb2 (оба None) копируются на карту. Поэтому даже после того, как пользователь "регистрирует" обратный вызов (назначая cb1), значение на карте еще None и ничего не вызывается.

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

5 ответов


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

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

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

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

и вместо:

f.cb1 = mycallback

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

f.register('one', mycallback)  

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

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

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

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

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

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

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