\[ [Index](index.md) | [Exercise 8.5](ex8_5.md) | [Exercise 9.1](ex9_1.md) \]

# Exercise 8.6

*Objectives:*

- Learn about delegating generators

*Files Modified:* `cofollow.py`, `server.py`

One potential issue in code that relies on generators is the problem
of hiding details from the user and writing libraries.  A lot of low-level
mechanics are generally required to drive everything and it's often rather
awkward to directly expose it to users.

Starting in Python 3.3, a new `yield from` statement can be used to
delegate generators to another function.  It is a useful way to
clean-up code that relies on generators.

## (a) Example: Receiving messages

In [Exercise 8.3](ex8_3.md), we looked at the definitions of coroutines.
Coroutines were functions that you sent data to.  For example:

```python
>>> from cofollow import consumer
>>> @consumer
    def printer():
        while True:
            item = yield
            print('Got:', item)

>>> p = printer()
>>> p.send('Hello')
Got: Hello
>>> p.send('World')
Got: World
>>>
```

At the time, it might have been interesting to use `yield` to receive a 
value.  However, if you really look at the code, it looks pretty weird--a
bare `yield` like that?  What's going on there?

In the `cofollow.py` file, define the following function:

```python
def receive(expected_type):
    msg = yield
    assert isinstance(msg, expected_type), 'Expected type %s' % (expected_type)
    return msg
```

This function receives a message, but then verifies that it is of an expected
type.  Try it:

```python
>>> from cofollow import consumer, receive
>>> @consumer
    def print_ints():
        while True:
             val = yield from receive(int)
             print('Got:', val)

>>> p = print_ints()
>>> p.send(42)
Got: 42
>>> p.send(13)
Got: 13
>>> p.send('13')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  ...
AssertionError: Expected type <class 'int'>
>>> 
```

From a readability point of view, the `yield from receive(int)` statement
is a bit more descriptive--it indicates that the function will yield until
it receives a message of a given type. 

Now, modify all of the coroutines in `coticker.py` to use the new `receive()`
function and make sure the code from [Exercise 8.3](ex8_3.md) still
works.

## (b) Wrapping a Socket

In the previous exercise, you wrote a simple network echo server using
generators.  The code for the server looked like this:

```python
def tcp_server(address, handler):
    sock = socket(AF_INET, SOCK_STREAM)
    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(5)
    while True:
        yield 'recv', sock
        client, addr = sock.accept()
        tasks.append(handler(client, addr))
        
def echo_handler(client, address):
    print('Connection from', address)
    while True:
        yield 'recv', client
        data = client.recv(1000)
        if not data:
            break
        yield 'send', client
        client.send(b'GOT:', data)
    print('Connection closed')
```

Create a class `GenSocket` that cleans up the `yield` statements and
allows the server to be rewritten more simply as follows:

```python
def tcp_server(address, handler):
    sock = GenSocket(socket(AF_INET, SOCK_STREAM))
    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(5)
    while True:
        client, addr = yield from sock.accept()
        tasks.append(handler(client, addr))
        
def echo_handler(client, address):
    print('Connection from', address)
    while True:
        data = yield from client.recv(1000)
        if not data:
            break
        yield from client.send(b'GOT:', data)
    print('Connection closed')
```

## (c) Async/Await

Take the `GenSocket` class you just wrote and wrap all of the methods 
that use `yield` with the `@coroutine` decorator from the `types` module.

```python
from types import coroutine
...

class GenSocket:
    def __init__(self, sock):
        self.sock = sock

    @coroutine
    def accept(self):
        yield 'recv', self.sock
        client, addr = self.sock.accept()
        return GenSocket(client), addr

    @coroutine
    def recv(self, maxsize):
        yield 'recv', self.sock
        return self.sock.recv(maxsize)

    @coroutine
    def send(self, data):
        yield 'send', self.sock
        return self.sock.send(data)

    def __getattr__(self, name):
        return getattr(self.sock, name)
```

Now, rewrite your server code to use `async` functions and `await` statements like this:

```python
async def tcp_server(address, handler):
    sock = GenSocket(socket(AF_INET, SOCK_STREAM))
    sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sock.bind(address)
    sock.listen(5)
    while True:
        client, addr = await sock.accept()
        tasks.append(handler(client, addr))
        
async def echo_handler(client, address):
    print('Connection from', address)
    while True:
        data = await client.recv(1000)
        if not data:
            break
        await client.send(b'GOT:', data)
    print('Connection closed')
```


\[ [Solution](soln8_6.md) | [Index](index.md) | [Exercise 8.5](ex8_5.md) | [Exercise 9.1](ex9_1.md) \]

----
`>>>` Advanced Python Mastery  
`...` A course by [dabeaz](https://www.dabeaz.com)  
`...` Copyright 2007-2023  

![](https://i.creativecommons.org/l/by-sa/4.0/88x31.png). This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)
