6

i was going through a post post about how observer pattern can be implemented in python . on the same post there are these comments.

1) In python you may as well just use plain functions, the ‘Observer’ class isnt really needed.

2) This is great example of what Java programmers are trying to do once they switch to Python – they feel like Python is missing all that crap and try to “port” it.

These comments imply that the observer pattern is not really useful in python and there exists other ways to achieve the same effect. is that true and if how can that be done?

Here is the code of observer pattern:

class Observable(object):

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

    def register(self, observer):
        if not observer in self.observers:
            self.observers.append(observer)

    def unregister(self, observer):
        if observer in self.observers:
            self.observers.remove(observer)

    def unregister_all(self):
        if self.observers:
            del self.observers[:]

    def update_observers(self, *args, **kwargs):
        for observer in self.observers:
            observer.update(*args, **kwargs)

from abc import ABCMeta, abstractmethod

class Observer(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def update(self, *args, **kwargs):
        pass

class AmericanStockMarket(Observer):
    def update(self, *args, **kwargs):
        print("American stock market received: {0}\n{1}".format(args, kwargs))

class EuropeanStockMarket(Observer):
    def update(self, *args, **kwargs):
        print("European stock market received: {0}\n{1}".format(args, kwargs))


if __name__ == "__main__":
    observable = Observable()

    american_observer = AmericanStockMarket()
    observable.register(american_observer)

    european_observer = EuropeanStockMarket()
    observable.register(european_observer)

    observable.update_observers('Market Rally', something='Hello World')
Mihail Russu
  • 2,526
  • 1
  • 17
  • 27
anekix
  • 2,393
  • 2
  • 30
  • 57
  • 3
    What problem are you trying to solve? Patterns are mere tools; trying to "implement" them is meaningless outside a specific context to apply them to. – spectras Jun 12 '17 at 12:33
  • @spectras i am trying to learn patterns so for that i need to implement it. but according t those comments this pattern is not really useful in python. and there exists alternate wasy to achieve the same idea – anekix Jun 12 '17 at 12:36
  • @spectras the context above is there exists two classes which subuscribe to main class and whenever update is made on the main class its propagated to all the subscriber classes. how can this be done in pythonic way( as mentioned in comments) – anekix Jun 12 '17 at 12:38
  • 2
    The pattern itself is useful, but the implementation is Java-esque in that you define an "interface" `Observer` that has a method that implementations must implement, so that the `Observable` can then call that method (update) of those implementations. In python you might as well just pass plain functions or bound methods as observers without all the boilerplate. This is not to say that you should never do it like this. "Interfaces "do have their uses in Python as well. – Ilja Everilä Jun 12 '17 at 12:39
  • 1
    The point made in the comments (which should be taken with a grain of salt, it's true... to a certain extent only) is you would not structure your python code the same way you structure a functionally-equivalent Java code. – spectras Jun 12 '17 at 12:40
  • @IljaEverilä can you give a very short example as i am not able to understand – anekix Jun 12 '17 at 12:41
  • @IljaEverilä i think the interface in above code is designed to enforce presence of an update method in an observer class. how can this be enforced without an interface? – anekix Jun 12 '17 at 12:43
  • 1
    By not enforcing anything, but accepting callables directly instead of "observer objects". You can even pass bound methods in python. – Ilja Everilä Jun 12 '17 at 12:45
  • @IljaEverilä i guess you are talking something about callbacks? like `some_function( callback_function(), **)`. if so can you please show how above code can be written using callables – anekix Jun 12 '17 at 12:49

1 Answers1

10

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.

spectras
  • 13,105
  • 2
  • 31
  • 53
  • 3
    If `register()` returned the fn, it'd work as a decorator as well, and so one could `@observable.register ...` functions. Just a small example of how Python might differ. – Ilja Everilä Jun 12 '17 at 13:07
  • 3
    @IljaEverilä> I feared it would be too much for this answer, especially given it's also a change in paradigm (you need the obervable instance to exist before the observer method is defined, which gets even further away from Java). But it is totally true and I would definitely have it return the callable in a real-world example. I added the return with a note. – spectras Jun 12 '17 at 13:13
  • I would be interested in that decorater solution. ;) I am not from Java but C++. – buhtz Jan 07 '18 at 13:30
  • @buhtz here you are. – spectras Jan 11 '18 at 00:55
  • I am not familar with decorators. ;) – buhtz Jan 11 '18 at 03:39
  • Well there are plenty excellent resources on the web to learn about them. Good place to start is the [official documentation](https://docs.python.org/3/glossary.html#term-decorator). Then a famous search engine comes up with [this](https://www.thecodeship.com/patterns/guide-to-python-function-decorators/). – spectras Jan 11 '18 at 11:57
  • Sorry I don't get it. I opened a new question about it with my example code https://stackoverflow.com/q/48336820/4865723. – buhtz Jan 19 '18 at 08:24
  • I agree entirely with this answer. What I want to discuss: Shouldn't `Foo.notify_observers` be private (`Foo._notify_observers`) if `Foo` is a generic base class so that `notify_observers` does not become part of the public API of derived classes? – moi Feb 15 '22 at 08:23
  • 1
    @moi that would be reasonable in plenty of apis yes. In a toy example like this it does not matter much. – spectras Feb 15 '22 at 09:44