human
I am working on the file "02-generators-revisited.ipynb".
The first task is:
Generators as suspendable/resumable program state
When executing a normal function, it begins execution as soon as it is called, continues (without giving up control) until it completes (by returning or throwing), and cleans up its call stack when exiting.
But with a generator, it gives up control every time it hits a yield, even if the generator isn't done executing yet. When it does so, then generator becomes suspended. So that its execution can be continued later, its locals() are saved on the generator object. Later, it can be resumed (by calling __next__(), send(), throw(), or close()), and its locals() will be restored before execution resumes from the yield point.
This has been implicitly assumed in previous examples. For example, in the section The basics of generators, the generator function _range_generator_function() relies on this mechanism in order to remember the values of stop and index between steps. There will be a more advanced example of this in the section Generators as coroutines.
There are other clever ways in which this can be utilized. One great usage of generators is in the implementation of @contextlib.contextmanager. This decorator combines the power of generators with the power of the context manager protocol, in order to produce something quite powerful.
@contextlib.contextmanager
Code sample from https://github.com/python/cpython/blob/v3.6.1/Lib/contextlib.py
Copyright (c) 2001-2017 Python Software Foundation.
All Rights Reserved.
License: Python license, https://www.python.org/3.6/license.html
Some modifications made so as to only highlight the interesting parts.
```python
def contextmanager(func):
    """@contextmanager decorator.
Typical usage:

    @contextmanager
    def some_generator(&lt;arguments&gt;):
        &lt;setup&gt;
        try:
            yield &lt;value&gt;
        finally:
            &lt;cleanup&gt;

This makes this:

    with some_generator(&lt;arguments&gt;) as &lt;variable&gt;:
        &lt;body&gt;

equivalent to this:

    &lt;setup&gt;
    try:
       ...
Can you write Python code for it?
assistant
#Here is the Python code you requested.

import collections

class StopAdder(Exception): pass

def adder_function():
    total = 0
    while True:
        print('At start of adder loop, current total is', total)
        try:
            integers = (yield total)
        except (Exception, GeneratorExit) as exc:
            print('Adder received exception', repr(exc), 'and is returning with final total', total)
            return total
        if not isinstance(integers, (list, tuple)):
            integers = [integers]
        if integers and isinstance(integers[0], collections.Iterable):
            integers = integers[0]
        print('Adder received', integers)
        total += sum(integers)

def send_values_into_adder(adder, *integers):
    print('Sending', integers, 'into adder')
    current_total = adder.send(integers)
    print('Current total in adder is', current_total)
    return current_total

adder = adder_function()
next(adder)

send_values_into_adder(adder)
print()
send_values_into_adder(adder, 10)
print()
send_values_into_adder(adder, 1, 2, 3)
print()
send_values_into_adder(adder, range(8))
print()
print('Sending StopAdder into adder')
try:
    adder.throw(StopAdder)
except StopIteration as exc:
    print('Final total from adder is', exc.value)