Подтвердить что ты не робот

Какой предпочтительный способ реализовать hook или callback в Python?

Я хотел бы предоставить возможность пользователям одного из моих модулей расширять свои возможности, предоставляя интерфейс для вызова пользовательской функции. Например, я хочу дать пользователям возможность получать уведомления, когда создается экземпляр класса, и предоставляется возможность изменить экземпляр до его использования.

Способ, которым я реализовал это, - объявить функцию уровня factory на уровне модуля, которая создает экземпляр:

# in mymodule.py
def factory(cls, *args, **kwargs):
    return cls(*args, **kwargs)

Тогда, когда мне нужен экземпляр класса в mymodule, я делаю factory(cls, arg1, arg2), а не cls(arg1, arg2).

Чтобы расширить его, программист напишет в другом модуле такую ​​функцию:

def myFactory(cls, *args, **kwargs):
    instance = myFactory.chain(cls, *args, **kwargs)
    # do something with the instance here if desired
    return instance

Установка вышеуказанного обратного вызова выглядит следующим образом:

myFactory.chain, mymodule.factory = mymodule.factory, myFactory

Для меня это кажется достаточно простым, но мне было интересно, будет ли вы, как программист на Python, ожидать, что функция зарегистрирует обратный вызов, а не делает это с назначением, или если бы были другие методы, которые вы ожидаете. Мое решение кажется работоспособным, идиоматичным и понятным для вас?

Я стараюсь держать его как можно проще; Я не думаю, что большинству приложений действительно потребуется цепочка более одного обратного вызова пользователя, например (хотя неограниченная цепочка поставляется "бесплатно" с приведенным выше шаблоном). Я сомневаюсь, что им нужно будет удалить обратные вызовы или указать приоритеты или порядок. Модули типа python-callbacks или PyDispatcher кажутся мне излишними, особенно последними, но если есть являются неотъемлемыми преимуществами для программиста, работающего с моим модулем, я открыт для них.

4b9b3361

Ответ 1

Объединив идею Aaron об использовании декоратора и идеи Ignacio класса, который поддерживает список подключенных обратных вызовов, плюс концепт, взятый из С#, я придумал следующее:

class delegate(object):

    def __init__(self, func):
        self.callbacks = []
        self.basefunc = func

    def __iadd__(self, func):
        if callable(func):
            self.__isub__(func)
            self.callbacks.append(func)
        return self

    def callback(self, func):
        if callable(func):
            self.__isub__(func)
            self.callbacks.append(func)
        return func

    def __isub__(self, func):
        try:
            self.callbacks.remove(func)
        except ValueError:
            pass
        return self

    def __call__(self, *args, **kwargs):
        result = self.basefunc(*args, **kwargs)
        for func in self.callbacks:
            newresult = func(result)
            result = result if newresult is None else newresult
        return result

Оформление функции с помощью @delegate позволяет прикрепить к ней другие функции.

@delegate
def intfactory(num):
    return int(num)

Функции могут быть добавлены к делегату с помощью += (и удалены с помощью -=). Вы можете также украсить funcname.callback, чтобы добавить функцию обратного вызова.

@intfactory.callback
def notify(num):
    print "notify:", num

def increment(num):
    return num+1

intfactory += increment
intfactory += lambda num: num * 2

print intfactory(3)   # outputs 8

Чувствует ли это Pythonic?

Ответ 2

Взяв aaronsterling idea немного дальше:

class C(object):
  _oncreate = []

  def __new__(cls):
    return reduce(lambda x, y: y(x), cls._oncreate, super(C, cls).__new__(cls))

  @classmethod
  def oncreate(cls, func):
    cls._oncreate.append(func)

c = C()
print hasattr(c, 'spew')

@C.oncreate
def spew(obj):
  obj.spew = 42
  return obj

c = C()
print c.spew

Ответ 3

Я мог бы использовать декоратор, чтобы пользователь мог просто написать.

@new_factory
def myFactory(cls, *args, **kwargs):
    instance = myFactory.chain(cls, *args, **kwargs)
    # do something with the instance here if desired
    return instance

Затем в вашем модуле

import sys

def new_factory(f):
    mod = sys.modules[__name__]
    f.chain = mod.factory
    mod.factory = f
    return f