How to use decorator in observer pattern for Pytho

2019-06-27 02:18发布

问题:

The observer pattern in the very simplified code below works well. I would like to have have a decorator @on_event that does the registration in the Observable singleton.

In class O2 below this does not work. The problem is of course that the decorator on_event get's called prior to the instance is created, and the registration will be to the unbound method event. In some way I have to delay the registration until the O2 object is initialized. Maybe needless to say but all I want to add in O2 is the decorator as in the code below.

But for sure there must be a solution to this? I have googled around but cannot find anything, and have tried several approaches.

class Observable(object):
    _instance = None

    @classmethod
    def instance(cls):
        if not cls._instance:
            cls._instance = Observable()
        return cls._instance

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

    def event(self, data):
        for fn in self.obs:
            fn(data)

def on_event(f):
    def wrapper(*args):
        return f(*args)
    Observable.instance().obs.append(f)
    return wrapper

class O(object):

    def __init__(self, name):
        self.name = name
        Observable.instance().obs.append(self.event)

    def event(self, data):
        print self.name + " Event: " + data

class O2(object):

    def __init__(self, name):
        self.name = name

    @on_event
    def eventx(self, data):
        print self.name + " Event: " + data

if __name__ == "__main__":
    o1 = O("o1")
    Observable.instance().event("E1")

    o2 = O2("o2")
    Observable.instance().event("E2")

回答1:

You cannot register bound methods until you have an instance for the method to be bound to. A function decorator alone does not have the context to detect when an instance has been created.

You could use a metaclass / decorator combined approach instead:

class ObservingMeta(type):
    def __call__(cls, *args, **kw):
         instance = super(ObservingMeta, cls).__call__(*args, **kw)
         for attr in vars(cls).values():
             if hasattr(attr, '__observer__'):
                 # register bound method
                 bound = attr.__get__(instance, cls)
                 Observable.instance().obs.append(bound)
         return instance

This registers all methods directly defined on cls that are marked as observers; the marking is done with a decorator:

def on_event_method(f):
    f.__observer__ = True
    return f

This is then used as:

class O2(object):
    __metaclass__ = ObservingMeta

    def __init__(self, name):
        self.name = name

    @on_event_method
    def eventx(self, data):
        print self.name + " Event: " + data

Do note that storing the methods in your Observable singleton keeps the instances alive; if you create instances of O2 the bound eventx methods reference the instance, and by keeping references to the methods that means the instances are never garbage collected if all other references to them are deleted.

See using python WeakSet to enable a callback functionality for how you could use weak references instead to only keep tabs on methods until the underlying instance has been deleted, without keeping the instances alive indefinitely.