PEP 342 (Coroutines via Enhanced Generators) added a throw()
method to generator objects, which allows the caller to raise an exception inside the generator (as if it was thrown by the yield
expression).
I am wondering what the use cases for this feature are.
With the throw() method the caller can signal to the generator that there is an abnormal situation that has to be corrected.) If the generator can raise an exception, intercepted by the caller, the reverse should also be possible.
Generators allow you to create iterators in a very pythonic manner. Iterators allow lazy evaluation, only generating the next element of an iterable object when requested. This is useful for very large data sets. Iterators and generators can only be iterated over once.
A generator is better in performance because it doesn't hold the values at all. If the generator had a lot of values that needed to convert to that list then you lose the performance in terms of it will put all of those values into memory.
Python generators are a simple way of creating iterators. All the work we mentioned above are automatically handled by generators in Python. Simply speaking, a generator is a function that returns an object (iterator) which we can iterate over (one value at a time).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With