Publish/subscribe event manager using weakrefs

Kamilche klachemin at comcast.net
Wed Feb 15 15:48:51 EST 2006


'''

event.py

An event manager using publish/subscribe, and weakrefs.
Any function can publish any event without registering it first, and
any
object can register interest in any event, even if it doesn't exist
yet.
The event manager uses weakrefs, so lists of listeners won't stop them
from being garbage collected when they're deleted.

Any suggestions are appreciated!

Sample output:

>>>

EVENT init (0 listeners, possibly dead) ('No one is interested in this
event',)
EVENT ball (0 listeners, possibly dead) ('Look! A ball, but no one is
watching.',)
*** Fido is interested in Bone
*** Fido is interested in Ball
EVENT ball (1 listeners, possibly dead) ('Another ball! Watch, Fido!',)
*** Fido noses the ball forward
*** Fido is dead.
EVENT ball (1 listeners, possibly dead) ('No one is watching because
Fido is dead.',)
Removing <weakref at 00CE9F90; dead> from ball
*** Snowball is interested in Ball
*** Snowball is interested in Yarn
*** Snowball is interested in Fluffy chick
*** Spot is interested in Bone
*** Spot is interested in Ball
EVENT bone (2 listeners, possibly dead) ('Only dogs eat bones.',)
Removing <weakref at 00CE9F90; dead> from bone
*** Spot slobbers on the bone
EVENT ball (2 listeners, possibly dead) ('Cats and dogs both love this
one!',)
*** Snowball chases the ball
*** Spot noses the ball forward
EVENT yarn (1 listeners, possibly dead) ('ball of pink yarn',)
*** Snowball bats at the yarn.
EVENT raking (0 listeners, possibly dead) ('Humans working in the
yard',)
EVENT fluffychick (1 listeners, possibly dead) ('Ooh, how cute! An
Easter leftover walking in the yard.',)
*** Snowball eats the fluffy chick.
Removing listener {'name': 'Snowball'} from event fluffychick
EVENT fluffychick (0 listeners, possibly dead) ('The cat is full and no
longer interested in chicks.',)
*** Snowball is dead.
*** Spot is dead.
EVENT ball (2 listeners, possibly dead) ('Ball anyone? Fluffy? Spot?
Fido?',)
Removing <weakref at 00CE9FC0; dead> from ball
Removing <weakref at 00CFA030; dead> from ball
Done!

>>>

'''

import weakref

_events = {}

def Subscribe(eventname, self):
    " Subscribe to an event, even if it doesn't exist yet."
    eventname = eventname.lower()
    eventname = eventname.replace(' ', '')
    if not eventname in _events:
        _events[eventname] = []
    listeners = _events[eventname]
    obj = weakref.ref(self)
    if not obj in listeners:
        listeners.append(obj)

def Unsubscribe(eventname, self):
    " Unsubscribe from an event, even if it never existed."
    eventname = eventname.lower()
    eventname = eventname.replace(' ', '')
    if not eventname in _events:
        return
    listeners = _events[eventname]
    obj = weakref.ref(self)
    if obj in listeners:
        print "Removing listener %s from event %s" % (str(self),
eventname)
        listeners.remove(obj)

def Raise(eventname, *args, **kwargs):
    " Publish an event, no need to register it first."
    eventname = eventname.lower()
    eventname = eventname.replace(' ', '')
    if not eventname in _events:
        _events[eventname] = []
    listeners = _events[eventname]
    print "EVENT %s (%d listeners, possibly dead) %s" % (eventname,
len(listeners), str(args))
    i = 0
    while i < len(listeners):
        obj = listeners[i]
        listener = obj()
        if listener:
            fnname = 'On' + eventname[0].upper() +
eventname[1:].lower()
            fn = getattr(listener, fnname, None)
            if fn == None:
                fn = getattr(listener, 'OnEvent')
            fn(eventname, *args, **kwargs)
            i += 1
        else:
            print "Removing %s from %s" % (str(obj), eventname)
            listeners.remove(obj)


class Listener:
    '''
    Objects that want to be notified of events.
    They should have an 'OnEventname' function for
    every event they're interested in, or a single
    function called 'OnEvent' to receive all events.
    '''
    _listen = []
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
        for eventname in self._listen:
            Subscribe(eventname, self)
            print "*** %s is interested in %s" % (self.name, eventname)
    def OnEvent(self, eventname, *args, **kwargs):
        print "*** %s(%s, %s)" % (eventname, str(args), str(kwargs))
    def __str__(self):
        return str(self.__dict__)
    def __del__(self):
        print "*** %s is dead." % self.name




def main():

    class Dog(Listener):
        name = 'Dog'
        _listen = ['Bone', 'Ball']
        def OnEvent(self, eventname, *args, **kwargs):
            if eventname == 'bone':
                print "*** %s slobbers on the bone" % self.name
            elif eventname == 'ball':
                print "*** %s noses the ball forward" % self.name

    class Cat(Listener):
        name = 'Cat'
        _listen = ['Ball', 'Yarn', 'Fluffy chick']
        def OnBall(self, *args, **kwargs):
            print "*** %s chases the ball" % self.name
        def OnYarn(self, *args, **kwargs):
            print "*** %s bats at the yarn." % self.name
        def OnFluffychick(self, *args, **kwargs):
            print "*** %s eats the fluffy chick." % self.name

    Raise('INIT', 'No one is interested in this event')
    Raise('BALL', 'Look! A ball, but no one is watching.')
    dog = Dog(name = 'Fido')
    Raise('BALL', 'Another ball! Watch, Fido!')
    del dog
    Raise('BALL', 'No one is watching because Fido is dead.')
    cat = Cat(name = 'Snowball')
    dog = Dog(name = 'Spot')
    Raise('BONE', "Only dogs eat bones.")
    Raise('BALL', "Cats and dogs both love this one!")
    Raise('YARN', "ball of pink yarn")
    Raise('RAKING', 'Humans working in the yard')
    Raise('Fluffy chick', 'Ooh, how cute! An Easter leftover walking in
the yard.')
    Unsubscribe('fluffy chick', cat)
    Raise('Fluffy chick', 'The cat is full and no longer interested in
chicks.')
    del cat
    del dog
    Raise('BALL', 'Ball anyone? Fluffy? Spot? Fido?')
    
    
    print "Done!"

if __name__ == '__main__':
    main()




More information about the Python-list mailing list