alternate ways to implement observer pattern in python
Asked Answered
Z

1

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')
Zanthoxylum answered 12/6, 2017 at 12:27 Comment(9)
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.Turrell
@Turrell 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 ideaZanthoxylum
@Turrell 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)Zanthoxylum
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.Flap
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.Turrell
@IljaEverilä can you give a very short example as i am not able to understandZanthoxylum
@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?Zanthoxylum
By not enforcing anything, but accepting callables directly instead of "observer objects". You can even pass bound methods in python.Flap
@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 callablesZanthoxylum
T
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.

Turrell answered 12/6, 2017 at 12:47 Comment(9)
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.Flap
@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.Turrell
I would be interested in that decorater solution. ;) I am not from Java but C++.Copyist
@Copyist here you are.Turrell
I am not familar with decorators. ;)Copyist
Well there are plenty excellent resources on the web to learn about them. Good place to start is the official documentation. Then a famous search engine comes up with this.Turrell
Sorry I don't get it. I opened a new question about it with my example code https://mcmap.net/q/1772744/-using-decorators-to-implement-observer-pattern-in-python3/4865723.Copyist
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?Vermiculate
@Vermiculate that would be reasonable in plenty of apis yes. In a toy example like this it does not matter much.Turrell

© 2022 - 2024 — McMap. All rights reserved.