Python中的事件系统

Jos*_*sip 180 python events dispatcher event-handling

您使用什么Python事件系统?我已经知道了pydispatcher,但我想知道还能找到什么,或者是常用的?

我对作为大型框架一部分的事件管理器不感兴趣,我宁愿使用一个我可以轻松扩展的小型简单解决方案.

flo*_*sla 142

结束答案中提到的各种事件系统:

最基本的事件系统风格是"处理程序方法包",它是Observer模式的简单实现.基本上,处理程序方法(callables)存储在一个数组中,并在事件"触发"时被调用.

  • zope.event显示了它的工作原理(参见Lennart的回答).注意:此示例甚至不支持处理程序参数.
  • LongPoke的"可调用列表"实现表明,通过子类化可以非常简单地实现这样的事件系统list.
  • spassig的EventHook(Michael Foord的事件模式)是一个简单的实现.
  • Josip的Valued Lessons事件类基本相同,但是使用a set而不是a list来存储包,并且实现了__call__两个合理的添加.
  • PyNotify在概念上类似,还提供了变量和条件的附加概念('变量变化事件').
  • axel基本上是一个处理程序包,具有更多与线程,错误处理,...相关的功能.

这些事件系统的缺点是您只能在实际的Event对象(或处理程序列表)上注册处理程序.因此,在注册时,事件已经存在.

这就是存在第二种事件系统风格的原因:发布 - 订阅模式.这里,处理程序不在事件对象(或处理程序列表)上注册,而是在中央调度程序上注册.通知程序也只与调度员交谈.听什么或发布什么是由'signal'决定的,它只不过是一个名字(字符串).

  • blinker有一些漂亮的功能,如自动断开和基于发送者的过滤.
  • PyPubSub乍一看似乎很简单.
  • PyDispatcher似乎强调多对多出版物等的灵活性.
  • 路易是一个经过改进的PyDispatcher"提供包括扭曲和PyQt的具体支持插件的基础设施".它似乎在2016年1月之后失去了维护.
  • django.dispatch是一个重写的PyDispatcher"具有更有限的接口,但性能更高".
  • Qt的信号和插槽可从PyQtPySide获得.它们在同一个线程中使用时作为回调,或者作为两个不同线程之间的事件(使用事件循环).信号和插槽具有以下限制:它们仅适用于派生自的类的对象QObject.

注意:threading.Event不是上述意义上的"事件系统".它是一个线程同步系统,其中一个线程等待,直到另一个线程"发出信号"Event对象.

注意:上面还没有包括pypydispatcher,python-dispatch插件的'钩子系统' 可能是有意义的.

  • 还有louie,它基于PyDispatcher:https://pypi.python.org/pypi/Louie/1.1 (2认同)
  • 弱引用的事件监听器是一个常见的需求。否则现实世界的使用会变得很困难。可能有用的解决方案支持的注释。 (2认同)

L̲̳*_*̲̳̳ 91

我一直在这样做:

class Event(list):
    """Event subscription.

    A list of callable objects. Calling an instance of this will cause a
    call to each item in the list in ascending order by index.

    Example Usage:
    >>> def f(x):
    ...     print 'f(%s)' % x
    >>> def g(x):
    ...     print 'g(%s)' % x
    >>> e = Event()
    >>> e()
    >>> e.append(f)
    >>> e(123)
    f(123)
    >>> e.remove(f)
    >>> e()
    >>> e += (f, g)
    >>> e(10)
    f(10)
    g(10)
    >>> del e[0]
    >>> e(2)
    g(2)

    """
    def __call__(self, *args, **kwargs):
        for f in self:
            f(*args, **kwargs)

    def __repr__(self):
        return "Event(%s)" % list.__repr__(self)
Run Code Online (Sandbox Code Playgroud)

然而,就像我见过的其他一切一样,没有自动生成的pydoc,没有签名,这真的很糟糕.

  • 我发现这种风格很有趣.这是甜蜜的骨头.我喜欢它允许人们将事件及其订阅者作为自主操作来操纵的事实.我会看到它在一个真实的项目中的表现如何. (3认同)
  • 非常漂亮的简约风格!超! (2认同)
  • 我不能对此表示足够的赞同,这确实非常简单。 (2认同)
  • 大忙,有人可以像我10岁时这样解释吗?此类是否被主类继承?我看不到__init__,所以不会使用super()。由于某种原因,它不是为我点击。 (2认同)

小智 64

我们使用Michael Foord在他的事件模式中建议的EventHook :

只需将EventHooks添加到您的类中:

class MyBroadcaster()
    def __init__():
        self.onChange = EventHook()

theBroadcaster = MyBroadcaster()

# add a listener to the event
theBroadcaster.onChange += myFunction

# remove listener from the event
theBroadcaster.onChange -= myFunction

# fire event
theBroadcaster.onChange.fire()
Run Code Online (Sandbox Code Playgroud)

我们添加了从对象中删除所有侦听器到Michaels类的功能,最后得到了以下结果:

class EventHook(object):

    def __init__(self):
        self.__handlers = []

    def __iadd__(self, handler):
        self.__handlers.append(handler)
        return self

    def __isub__(self, handler):
        self.__handlers.remove(handler)
        return self

    def fire(self, *args, **keywargs):
        for handler in self.__handlers:
            handler(*args, **keywargs)

    def clearObjectHandlers(self, inObject):
        for theHandler in self.__handlers:
            if theHandler.im_self == inObject:
                self -= theHandler
Run Code Online (Sandbox Code Playgroud)

  • 最后一个方法是错误的,因为自我.__处理程序在迭代期间被修改.修复:`self .__ handlers = [h for self in .__ handlers if h.im_self!= obj] (6认同)

Len*_*bro 20

我用zope.event.这是你能想象到的最无聊的骨头.:-)实际上,这里是完整的源代码:

subscribers = []

def notify(event):
    for subscriber in subscribers:
        subscriber(event)
Run Code Online (Sandbox Code Playgroud)

请注意,例如,您无法在进程之间发送消息.它不是一个消息系统,只是一个事件系统,仅此而已.

  • http://pypi.python.org/pypi/zope.event ...为了拯救可怜的谷歌一些带宽;-) (16认同)

Jos*_*sip 14

我在Valued Lessons上发现了这个小脚本.它似乎具有我所追求的正确的简单/功率比.Peter Thatcher是以下代码的作者(未提及许可).

class Event:
    def __init__(self):
        self.handlers = set()

    def handle(self, handler):
        self.handlers.add(handler)
        return self

    def unhandle(self, handler):
        try:
            self.handlers.remove(handler)
        except:
            raise ValueError("Handler is not handling this event, so cannot unhandle it.")
        return self

    def fire(self, *args, **kargs):
        for handler in self.handlers:
            handler(*args, **kargs)

    def getHandlerCount(self):
        return len(self.handlers)

    __iadd__ = handle
    __isub__ = unhandle
    __call__ = fire
    __len__  = getHandlerCount

class MockFileWatcher:
    def __init__(self):
        self.fileChanged = Event()

    def watchFiles(self):
        source_path = "foo"
        self.fileChanged(source_path)

def log_file_change(source_path):
    print "%r changed." % (source_path,)

def log_file_change2(source_path):
    print "%r changed!" % (source_path,)

watcher              = MockFileWatcher()
watcher.fileChanged += log_file_change2
watcher.fileChanged += log_file_change
watcher.fileChanged -= log_file_change2
watcher.watchFiles()
Run Code Online (Sandbox Code Playgroud)


小智 8

你可以看看pymitter(pypi).它是一个小的单文件(~250 loc)方法"提供名称空间,通配符和TTL".

这是一个基本的例子:

from pymitter import EventEmitter

ee = EventEmitter()

# decorator usage
@ee.on("myevent")
def handler1(arg):
   print "handler1 called with", arg

# callback usage
def handler2(arg):
    print "handler2 called with", arg
ee.on("myotherevent", handler2)

# emit
ee.emit("myevent", "foo")
# -> "handler1 called with foo"

ee.emit("myotherevent", "bar")
# -> "handler2 called with bar"
Run Code Online (Sandbox Code Playgroud)


Cri*_*cia 7

我创建了一个EventManager类(最后的代码).语法如下:

#Create an event with no listeners assigned to it
EventManager.addEvent( eventName = [] )

#Create an event with listeners assigned to it
EventManager.addEvent( eventName = [fun1, fun2,...] )

#Create any number event with listeners assigned to them
EventManager.addEvent( eventName1 = [e1fun1, e1fun2,...], eventName2 = [e2fun1, e2fun2,...], ... )

#Add or remove listener to an existing event
EventManager.eventName += extra_fun
EventManager.eventName -= removed_fun

#Delete an event
del EventManager.eventName

#Fire the event
EventManager.eventName()
Run Code Online (Sandbox Code Playgroud)

这是一个例子:

def hello(name):
    print "Hello {}".format(name)

def greetings(name):
    print "Greetings {}".format(name)

EventManager.addEvent( salute = [greetings] )
EventManager.salute += hello

print "\nInitial salute"
EventManager.salute('Oscar')

print "\nNow remove greetings"
EventManager.salute -= greetings
EventManager.salute('Oscar')
Run Code Online (Sandbox Code Playgroud)

输出:

最初的致敬
问候奥斯卡
你好奥斯卡

现在删除问候
Hello Oscar

EventManger代码:

class EventManager:

    class Event:
        def __init__(self,functions):
            if type(functions) is not list:
                raise ValueError("functions parameter has to be a list")
            self.functions = functions

        def __iadd__(self,func):
            self.functions.append(func)
            return self

        def __isub__(self,func):
            self.functions.remove(func)
            return self

        def __call__(self,*args,**kvargs):
            for func in self.functions : func(*args,**kvargs)

    @classmethod
    def addEvent(cls,**kvargs):
        """
        addEvent( event1 = [f1,f2,...], event2 = [g1,g2,...], ... )
        creates events using **kvargs to create any number of events. Each event recieves a list of functions,
        where every function in the list recieves the same parameters.

        Example:

        def hello(): print "Hello ",
        def world(): print "World"

        EventManager.addEvent( salute = [hello] )
        EventManager.salute += world

        EventManager.salute()

        Output:
        Hello World
        """
        for key in kvargs.keys():
            if type(kvargs[key]) is not list:
                raise ValueError("value has to be a list")
            else:
                kvargs[key] = cls.Event(kvargs[key])

        cls.__dict__.update(kvargs)
Run Code Online (Sandbox Code Playgroud)


Pit*_*kos 6

这是一个应该很好的最小设计.你要做的就是简单地继承Observer一个类,然后用它observe(event_name, callback_fn)来监听一个特定的事件.只要在代码中的任何地方触发该特定事件(即.Event('USB connected')),就会触发相应的回调.

class Observer():
    _observers = []
    def __init__(self):
        self._observers.append(self)
        self._observed_events = []
    def observe(self, event_name, callback_fn):
        self._observed_events.append({'event_name' : event_name, 'callback_fn' : callback_fn})


class Event():
    def __init__(self, event_name, *callback_args):
        for observer in Observer._observers:
            for observable in observer._observed_events:
                if observable['event_name'] == event_name:
                    observable['callback_fn'](*callback_args)
Run Code Online (Sandbox Code Playgroud)

例:

class Room(Observer):
    def __init__(self):
        print("Room is ready.")
        Observer.__init__(self) # DON'T FORGET THIS
    def someone_arrived(self, who):
        print(who + " has arrived!")

# Observe for specific event
room = Room()
room.observe('someone arrived',  room.someone_arrived)

# Fire some events
Event('someone left',    'John')
Event('someone arrived', 'Lenard') # will output "Lenard has arrived!"
Event('someone Farted',  'Lenard')
Run Code Online (Sandbox Code Playgroud)


Fel*_*elk 5

我做了一个Longpoke的简约方法的变体,也确保了callees和callers的签名:

class EventHook(object):
    '''
    A simple implementation of the Observer-Pattern.
    The user can specify an event signature upon inizializazion,
    defined by kwargs in the form of argumentname=class (e.g. id=int).
    The arguments' types are not checked in this implementation though.
    Callables with a fitting signature can be added with += or removed with -=.
    All listeners can be notified by calling the EventHook class with fitting
    arguments.

    >>> event = EventHook(id=int, data=dict)
    >>> event += lambda id, data: print("%d %s" % (id, data))
    >>> event(id=5, data={"foo": "bar"})
    5 {'foo': 'bar'}

    >>> event = EventHook(id=int)
    >>> event += lambda wrong_name: None
    Traceback (most recent call last):
        ...
    ValueError: Listener must have these arguments: (id=int)

    >>> event = EventHook(id=int)
    >>> event += lambda id: None
    >>> event(wrong_name=0)
    Traceback (most recent call last):
        ...
    ValueError: This EventHook must be called with these arguments: (id=int)
    '''
    def __init__(self, **signature):
        self._signature = signature
        self._argnames = set(signature.keys())
        self._handlers = []

    def _kwargs_str(self):
        return ", ".join(k+"="+v.__name__ for k, v in self._signature.items())

    def __iadd__(self, handler):
        params = inspect.signature(handler).parameters
        valid = True
        argnames = set(n for n in params.keys())
        if argnames != self._argnames:
            valid = False
        for p in params.values():
            if p.kind == p.VAR_KEYWORD:
                valid = True
                break
            if p.kind not in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY):
                valid = False
                break
        if not valid:
            raise ValueError("Listener must have these arguments: (%s)"
                             % self._kwargs_str())
        self._handlers.append(handler)
        return self

    def __isub__(self, handler):
        self._handlers.remove(handler)
        return self

    def __call__(self, *args, **kwargs):
        if args or set(kwargs.keys()) != self._argnames:
            raise ValueError("This EventHook must be called with these " +
                             "keyword arguments: (%s)" % self._kwargs_str())
        for handler in self._handlers[:]:
            handler(**kwargs)

    def __repr__(self):
        return "EventHook(%s)" % self._kwargs_str()
Run Code Online (Sandbox Code Playgroud)