Как сделать выполнение функции как Event-событие?

Здравствуйте, подскажите пожалуйста.
# file1.py

class TEMP1(object):
    def __init__(self): pass
    def func1(self, *a, **k):
        print 'TEMP1 > func1'
        
t1 = TEMP1()
        
# file2.py

#from file1 import TEMP1

def decorate(function):
    print 'start decorate'
    def wrap(*a, **k):
        print 'start wrap'
        return function(*a, **k)
    return wrap

@decorate
def new_func1(*a, **k):
    old_func1(*a, **k)
    print 'hook: new_func1'
    
old_func1 = TEMP1.func1
TEMP1.func1 = new_func1

def myFunc():
    print 'myFunc'

t1.func1()


Как в функции decorate определить нашу function как Event (если я правильно понимаю), чтобы к ней можно было прописать function += myFunc?
  • Вопрос задан
  • 401 просмотр
Пригласить эксперта
Ответы на вопрос 1
Vindicar
@Vindicar
RTFM!
Погоди-погоди-погоди... если я правильно понимаю, ты хочешь создать класс со следующими свойствами:
1. Экземпляры класса должны хранить в себе список пользовательских callback-функций с одинаковой сигнатурой.
2. При вызове экземпляра этого класса (как callable object), все callback-функции должны быть вызваны поочерёдно.
3. Добавить и удалить новый callback должно быть можно посредством операторов += и -=.
Я верно понял? Тогда мне неясно причем тут декоратор, и что должна будет делать декорируемая функция.

Я бы написал что-то в духе:
class EventCaller:
    #конструктор
    def __init__(self):
        #поскольку мы не планируем позволять двойную подписку, то у нас будет множество, а не список.
        self.__callbacks = set();
    # магический метод перегрузки оператора +=
    def __iadd__(self, callback):
        #проверка, что нам вообще передали callable object
        if not callable(callback):
            raise Exception("Callback object is not callable")
        #эта проверка не позволяет подписаться дважды одним объектом
        if callback in self.__callbacks:
            raise Exception("Double event subscription")
        self.__callbacks.add(callback)
    #магический метод перегрузки оператора -=
    def __isub__(self, callback):
        if callback not in self.__callbacks:
            raise Exception("No such event handler")
        self.__callbacks.remove(callback)
    #магический метод, позволяющий вызывать экземпляр объекта как функцию
    #аргументы, переданные методу, сохраняются как есть
    #неименованные - в кортеже args, именованные - в словаре kwargs
    def __call__(self, *args, **kwargs):
        for callback in self.__callbacks:
            try:
                callback(*args, **kwargs)
            except:
                pass


Вот черновик такого класса. Он, конечно, нуждается в доработке:
а) стоит добавить потоко-безопасную работу со списком обработчиков. Возможно, заменить список на потоко-безопасное хранилище.
б) стоит запоминать выкинутые обработчиками исключения, и сообщать о них тем или иным способом (перевыбрасывать? выбрасывать своё исключение со списком всех произошедших?)
в) так что если один из обработчиков изменит переданные объекты, это повлияет на последующие. Если такое поведение надо избежать, возможно, стоит создавать отдельную глубокую копию (см. модуль copy) args и kwargs для каждого обработчика. Хотя там свои заморочки.

Пример использования:
#простой обработчик, печатающий переданные ему аргументы
def evthandler(*args, **kwargs):
    print "Unnamed arguments are:", args
    print "Named arguments are:", kwargs
#
#создаем объект-событие
evt = EventCaller()
#подписываемся на него
evt += evthandler
#генерируем событие. Аргументы могут быть произвольными, они будут переданы обработчикам как есть
evt("unnamed arg 1", "unnamed arg 2", kwarg1=True, kwarg2=42)
#отписываемся
evt -= evthandler
#этот вызов не будет иметь эффекта, так как список обработчиков события пуст
evt("unnamed arg 1", "unnamed arg 2", kwarg1=True, kwarg2=42)


За синтаксическую правильность кода не ручаюсь, но идея должна быть ясна.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы