Search code examples
pythonproducer-consumercoroutineobserver-pattern

What is offered by coroutines in python that improve a naive consumer/producer setup?


I've read a little about coroutines, in particular with python, and something is not entirely obvious to me.

I have implemented a producer/consumer model, a basic version of which is as follows:

#!/usr/bin/env python

class MyConsumer(object):

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

    def __call__(self, data):
        return self.observer(data)

    def observer(self, data):
        print self.__name + ': ' + str(data)

class MyProducer(object):

    def __init__(self):
        self.__observers = []
        self.__counter = 0

    def add_observer(self, observer):
        self.__observers.append(observer)

    def run(self):
        while self.__counter < 10:
            for each_observer in self.__observers:
                each_observer(self.__counter)

            self.__counter += 1

def main():

    consumer_one = MyConsumer('consumer one')
    consumer_two = MyConsumer('consumer two')
    producer = MyProducer()

    producer.add_observer(consumer_one)
    producer.add_observer(consumer_two)

    # run
    producer.run()

if __name__ == "__main__":
    main()

Obviously, MyConsumer could have routines for producing as well and so a data pipeline can be built easily. As I have implemented this in practice, a base class is defined that implements the logic of the consumer/producer model and single processing function is implemented that is overwritten in child classes. This makes it very simple to produce data pipelines with easily defined, isolated processing elements.

This seems to me to be typical of the kinds of applications that are presented for coroutines, for example in the oft quoted tutorial: http://www.dabeaz.com/coroutines/index.html. Unfortunately, it is not apparent to me what the advantages of coroutines are over the implementation above. I can see that in languages in which callable objects are more difficult to handle, there is something to be gained, but in the case of python, this doesn't seem to be an issue.

Can anybody shed some light on this for me? Thanks.

edit: Apologies, the producer in the above code counts from 0 to 9 and notifies the consumers, which then print out their name followed by the count value.


Solution

  • When using the coroutines approach, both the consumer and the producer code can be simpler sometimes. In your approach, at least one of them must be written as a finite-state-machine (assuming some state is involved).

    With the coroutines approach they are essentially independent processes.

    An example would help:

    Take the example you provided but now assume the consumer prints only every 2nd input. Your approach requires adding an object member indicating whether the received input is an odd or even sample.

    def observer(self, data):
        self.odd_sample = !self.odd_sample
        if self.odd_sample:
            print str(data)
    

    When using a coroutine, one would just loop over the input, dropping every second input. The 'state' is implicitly maintained by the current position in the code:

    while True:
        y = producer()
        print(y)
        y = producer()
        # ignore this value