简体   繁体   中英

Python observable implementation that supports multi-channel subscribers

In a twisted application I have a series of resource controller/manager classes that interact via the Observable pattern. Generally most observers will subscribe to a specific channel (ex. "foo.bar.entity2") but there are a few cases where I'd like to know about all event in a specific channel (ex. "foo.*" ) so I wrote something like the following:

    from collections import defaultdict

    class SimplePubSub(object):

        def __init__(self):
            self.subjects = defaultdict(list)


        def subscribe(self, subject, callbackstr):
            """
                for brevity, callbackstr would be a valid Python function or bound method but here is just a string
            """
            self.subjects[subject].append(callbackstr)

        def fire(self, subject):
            """
                Again for brevity, fire would have *args, **kwargs or some other additional message arguments but not here            
            """

            if subject in self.subjects:
                print "Firing callback %s" % subject
                for callback in self.subjects[subject]:
                    print "callback %s" % callback



    pubSub = SimplePubSub()
    pubSub.subscribe('foo.bar', "foo.bar1")
    pubSub.subscribe('foo.foo', "foo.foo1")

    pubSub.subscribe('foo.ich.tier1', "foo.ich.tier3_1")
    pubSub.subscribe('foo.ich.tier2', "foo.ich.tier2_1")
    pubSub.subscribe('foo.ich.tier3', "foo.ich.tier2_1")

    #Find everything that starts with foo
    #say foo.bar is fired
    firedSubject = "foo.bar"
    pubSub.fire(firedSubject)
    #outputs 
    #>>Firing callback foo.bar
    #>>callback foo.bar1

    #but let's say I want to add a callback for everything undr foo.ich

    class GlobalPubSub(SimplePubSub):

        def __init__(self):
            self.globals = defaultdict(list)
            super(GlobalPubSub, self).__init__()


        def subscribe(self, subject, callback):
            if subject.find("*") > -1:
                #assumes global suscriptions would be like subject.foo.* and we want to catch all subject.foo's 
                self.globals[subject[:-2]].append(callback)
            else:
                super(GlobalPubSub, self).subscribe(subject, callback)

        def fire(self, subject):
            super(GlobalPubSub, self).fire(subject)
            if self.globals:
                for key in self.globals.iterkeys():
                    if subject.startswith(key):
                        for callback in self.globals[key]:
                            print "global callback says", callback

    print "Now with global subscriptions"
    print
    pubSub = GlobalPubSub()
    pubSub.subscribe('foo.bar', "foo.bar1")
    pubSub.subscribe('foo.foo', "foo.foo1")

    pubSub.subscribe('foo.ich.tier1', "foo.ich.tier3_1")
    pubSub.subscribe('foo.ich.tier2', "foo.ich.tier2_1")
    pubSub.subscribe('foo.ich.tier3', "foo.ich.tier2_1")
    pubSub.subscribe("foo.ich.*", "All the ichs, all the time!")

    #Find everything that starts with foo.ich
    firedSubject = "foo.ich.tier2"
    pubSub.fire(firedSubject)
    #outputs 
    #>>Firing callback foo.bar
    #>>callback foo.bar1
    #>>Now with global subscriptions
    #
    #>>Firing callback foo.ich.tier2
    #>>callback foo.ich.tier2_1
    #>>global callback says All the ichs, all the time!

Is this as good as it gets without resorting to some sort of exotic construct ( tries for example )? I'm looking for an affirmation that I'm on the right track or a better alternative suggestion on a global subscription handler that's pure python ( no external libraries or services ).

This looks like you are on the right track to me. I was using PyPubSub with a wxPython app for a bit, and then ended up implementing my own "more simple" version that, at its root, looks very similar to what you've done here except with a few more bells and whistles that you'd probably end up implementing as you fill out your requirements.

The answer given here is also lot like what you have done as well.

This answer goes into examples that are a bit different approach.

There are a number existing libraries besides PyPubSub out there, such as pydispatch and blinker , that might be worth looking at for reference or ideas.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM