5.4 KiB
[ Index | Exercise 8.5 | Exercise 9.1 ]
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, we looked at the definitions of coroutines. Coroutines were functions that you sent data to. For example:
>>> from cofollow import consumer
>>> @consumer
def printer():
while True:
= yield
item 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:
def receive(expected_type):
= yield
msg 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:
>>> from cofollow import consumer, receive
>>> @consumer
def print_ints():
while True:
= yield from receive(int)
val print('Got:', val)
>>> p = print_ints()
>>> p.send(42)
42
Got: >>> p.send(13)
13
Got: >>> p.send('13')
Traceback (most recent call last):"<stdin>", line 1, in <module>
File
...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 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:
def tcp_server(address, handler):
= socket(AF_INET, SOCK_STREAM)
sock 1)
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR,
sock.bind(address)5)
sock.listen(while True:
yield 'recv', sock
= sock.accept()
client, addr
tasks.append(handler(client, addr))
def echo_handler(client, address):
print('Connection from', address)
while True:
yield 'recv', client
= client.recv(1000)
data if not data:
break
yield 'send', client
b'GOT:', data)
client.send(print('Connection closed')
Create a class GenSocket
that cleans up the
yield
statements and allows the server to be rewritten more
simply as follows:
def tcp_server(address, handler):
= GenSocket(socket(AF_INET, SOCK_STREAM))
sock 1)
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR,
sock.bind(address)5)
sock.listen(while True:
= yield from sock.accept()
client, addr
tasks.append(handler(client, addr))
def echo_handler(client, address):
print('Connection from', address)
while True:
= yield from client.recv(1000)
data 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.
from types import coroutine
...
class GenSocket:
def __init__(self, sock):
self.sock = sock
@coroutine
def accept(self):
yield 'recv', self.sock
= self.sock.accept()
client, addr 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:
async def tcp_server(address, handler):
= GenSocket(socket(AF_INET, SOCK_STREAM))
sock 1)
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR,
sock.bind(address)5)
sock.listen(while True:
= await sock.accept()
client, addr
tasks.append(handler(client, addr))
async def echo_handler(client, address):
print('Connection from', address)
while True:
= await client.recv(1000)
data if not data:
break
await client.send(b'GOT:', data)
print('Connection closed')
[ Solution | Index | Exercise 8.5 | Exercise 9.1 ]
>>>
Advanced Python Mastery
...
A course by dabeaz
...
Copyright 2007-2023
.
This work is licensed under a Creative Commons
Attribution-ShareAlike 4.0 International License