Let’s say I use a generator to handle adding information to a database; I use this to store network-received information, and by using a generator I can do this efficiently whenever I actually receive data, and do other things otherwise.
So, my generator first opens a database connection, and every time you send it something, it’ll add a row:
def add_to_database(connection_string):
db = mydatabaselibrary.connect(connection_string)
cursor = db.cursor()
while True:
row = yield
cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row)
That is all fine and well; every time I .send()
my data it’ll insert a row.
But what if my database is transactional? How do I signal this generator when to commit the data to the database? And when to abort the transaction? Moreover, it is holding an open connection to the database, maybe I sometimes want it to close that connection to reclaim resources.
This is where the .throw()
method comes in; with .throw()
I can raise exceptions in that method to signal certain circumstances:
def add_to_database(connection_string):
db = mydatabaselibrary.connect(connection_string)
cursor = db.cursor()
try:
while True:
try:
row = yield
cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row)
except CommitException:
cursor.execute('COMMIT')
except AbortException:
cursor.execute('ABORT')
finally:
cursor.execute('ABORT')
db.close()
The .close()
method on a generator does essentially the same thing; it uses the GeneratorExit
exception combined with .throw()
to close a running generator.
All this is an important underpinning of how coroutines work; coroutines are essentially generators, together with some additional syntax to make writing a coroutine easier and clearer. But under the hood they are still built on the same yielding, and sending. And when you are running multiple coroutines in parallel, you need a way to cleanly exit those coroutines if one of them has failed, just to name an example.