Python Observer Pattern: Examples, Tips

Python Observer Pattern: Examples, Tips?

However it does lack flexibility.

Well... actually, this looks like a good design to me if an asynchronous API is what you want. It usually is. Maybe all you need is to switch from stderr to Python's logging module, which has a sort of publish/subscribe model of its own, what with Logger.addHandler() and so on.

If you do want to support observers, my advice is to keep it simple. You really only need a few lines of code.

class Event(object):
pass

class Observable(object):
def __init__(self):
self.callbacks = []
def subscribe(self, callback):
self.callbacks.append(callback)
def fire(self, **attrs):
e = Event()
e.source = self
for k, v in attrs.items():
setattr(e, k, v)
for fn in self.callbacks:
fn(e)

Your Job class can subclass Observable. When something of interest happens, call self.fire(type="progress", percent=50) or the like.

alternate ways to implement observer pattern in python

There are many different ways you can "observe" something in python. Use property descriptors, custom __setattr__, decorators...

Here is a simple example that uses first class functions:

class Foo(object):
def __init__(self):
self.observers = []

def register(self, fn):
self.observers.append(fn)
return fn # <-- See comments below answer

def notify_observers(self, *args, **kwargs):
for fn in self.observers:
fn(*args, **kwargs)

You can then register any callable.

class Bar(object):
def do_something(self, *args, **kwargs):
pass # do something

foo = Foo()
bar = Bar()
foo.register(bar.do_something)

This will work properly. The call to do_something will have the correct self value. Because an object's methods are callable objects which carry a reference to the instance they are bound to.

This might help understanding how it works under the hood:

>>> bar
<Bar object at 0x7f3fec4a5a58>
>>> bar.do_something
<bound method Bar.do_something of <Bar object at 0x7f3fec4a5a58>>
>>> type(bar.do_something)
<class 'method'>
>>> bar.do_something.__self__
<Bar object at 0x7f3fec4a5a58>

[edit: decorator example]

You may also use the register method we defined above as a decorator, like this:

foo = Foo()

@foo.register
def do_something(*args, **kwargs):
pass # do something

For this to work, just remember that register needs to return the callable it registered.

Python Observer Design Pattern

There are already some nice sources of information about design patterns in Python.

Here's an interesting book on Python design patterns.

Last but not least...Alex Martelli gave a very interesting talk regarding this issue for Google Developer Day US.

Edit:
After reading that this is a duplicate of another StackOverflow question, I would recommend that everyone read that one as well. There are a lot of useful links and comments there.

Using decorators to implement Observer Pattern in Python3

To register the callback, you need to have an actual object. In your code, how is @Observable.register_observer supposed to find which instance is should register on?

Please drop that Observable thing that's a javaism, cumbersome in python.

Look at this.

#!/usr/bin/env python

class SomeData(object):
def __init__(self, value):
self.callbacks = []
self.foo = value

def register(self, callback):
self.callbacks.append(callback)
return callback

def notify(self, *args, **kwargs):
for callback in self.callbacks:
callback(self, *args, **kwargs)

class SomeGUI(object):
def redraw(self, obj, key, newvalue):
print('redrawing %s with value %s' % (self, newvalue))

if __name__ == '__main__':
my_data = SomeData(42)

# Register some function using decorator syntax
@my_data.register
def print_it(obj, key, value):
print('Key %s changed to %s' % (key, value))

# Register the SomeGUI element
my_gui = SomeGUI()
my_data.register(my_gui.redraw)

# Try changing it. Note my_data is dumb for now, notify manually.
my_data.foo = 10
my_data.notify("foo", 10)

I intentionally removed automatic notifications to illustrate registration by itself.

Let's add it back. But there is no point using that Observable class. Let's make it lighter, simply defining an event class.

#!/usr/bin/env python3

class Event(object):
def __init__(self):
self.callbacks = []

def notify(self, *args, **kwargs):
for callback in self.callbacks:
callback(*args, **kwargs)

def register(self, callback):
self.callbacks.append(callback)
return callback

class SomeData(object):
def __init__(self, foo):
self.changed = Event()
self._foo = foo

@property
def foo(self):
return self._foo

@foo.setter
def foo(self, value):
self._foo = value
self.changed.notify(self, 'foo', value)

class SomeGUI(object):
def redraw(self, obj, key, newvalue):
print('redrawing %s with value %s' % (self, newvalue))

if __name__ == '__main__':
my_data = SomeData(42)

# Register some function using decorator syntax
@my_data.changed.register
def print_it(obj, key, value):
print('Key %s changed to %s' % (key, value))

# Register the SomeGUI element
my_gui = SomeGUI()
my_data.changed.register(my_gui.redraw)

# Try changing it.
my_data.foo = 10

As you probably noted now, the decorator syntax is useful in those circumstances:

  • You have a single registry. Either a singleton or the class itself class are first-order objects, and most are singletons.
  • You dynamically define the function and register it as you go.

Now, those manual getters/setters you have are cumbersome as well, if you have many why not factor them out?

#!/usr/bin/env python3

class Event(object):
def __init__(self):
self.callbacks = []

def notify(self, *args, **kwargs):
for callback in self.callbacks:
callback(*args, **kwargs)

def register(self, callback):
self.callbacks.append(callback)
return callback

@classmethod
def watched_property(cls, event_name, key):
actual_key = '_%s' % key

def getter(obj):
return getattr(obj, actual_key)

def setter(obj, value):
event = getattr(obj, event_name)
setattr(obj, actual_key, value)
event.notify(obj, key, value)

return property(fget=getter, fset=setter)

class SomeData(object):
foo = Event.watched_property('changed', 'foo')

def __init__(self, foo):
self.changed = Event()
self.foo = foo

class SomeGUI(object):
def redraw(self, obj, key, newvalue):
print('redrawing %s with value %s' % (self, newvalue))

if __name__ == '__main__':
my_data = SomeData(42)

# Register some function using decorator syntax
@my_data.changed.register
def print_it(obj, key, value):
print('Key %s changed to %s' % (key, value))

# Register the SomeGUI element
my_gui = SomeGUI()
my_data.changed.register(my_gui.redraw)

# Try changing it.
my_data.foo = 10

For reference, all three programs output the exact same thing:

$ python3 test.py
Key foo changed to 10
redrawing <__main__.SomeGUI object at 0x7f9a90d55fd0> with value 10

Python Observer Pattern: Examples, Tips?

However it does lack flexibility.

Well... actually, this looks like a good design to me if an asynchronous API is what you want. It usually is. Maybe all you need is to switch from stderr to Python's logging module, which has a sort of publish/subscribe model of its own, what with Logger.addHandler() and so on.

If you do want to support observers, my advice is to keep it simple. You really only need a few lines of code.

class Event(object):
pass

class Observable(object):
def __init__(self):
self.callbacks = []
def subscribe(self, callback):
self.callbacks.append(callback)
def fire(self, **attrs):
e = Event()
e.source = self
for k, v in attrs.items():
setattr(e, k, v)
for fn in self.callbacks:
fn(e)

Your Job class can subclass Observable. When something of interest happens, call self.fire(type="progress", percent=50) or the like.



Related Topics



Leave a reply



Submit