returns to its caller. If the generator yields a value, a$_{RuntimeError}$ is raised. If the generator raises any other exception, it is propagated to the caller. close() does nothing if the generator has already exited due to an exception or normal exit. Here is a simple example that demonstrates the behavior of generators and generator functions: Examples The Python Language Reference, Release 3.9.5 >>> def echo (value = None ): ... print("Execution starts when'next() 'is called for the first time. ") ... try : ... while True : ... try : ... value=( yield value) ... except Exception as e: ... value=e ... finally : ... print("Don't forget to clean up when'close() 'is called. ") ... >>> generator =echo(1) >>> print(next(generator)) Execution starts when 'next()' is called for the first time. 1 >>> print(next(generator)) None >>> print(generator.send(2)) 2 >>> generator.throw(TypeError,"spam") TypeError('spam',) >>> generator.close() Don't forget to clean up when 'close()' is called. For examples using$_{yield from}$, see pep-380 in “What’s New in Python.” The presence of a yield expression in a function or method defined using$_{async def}$further defines the function as an asynchronous generator function. When an asynchronous generator function is called, it returns an asynchronous iterator known as an asynchronous generator object. That object then controls the execution of the generator function. An asynchronous generator object is typically used in an$_{async for}$statement in a coroutine function analogously to how a generator object would be used in a$_{for}$ statement. Calling one of the asynchronous generator’s methods returns an awaitable object, and the execution starts when this object is awaited on. At that time, the execution proceeds to the first yield expression, where it is suspended again, returning the value of$_{expression_list}$to the awaiting coroutine. As with a generator, suspension means that all local state is retained, including the current bindings of local variables, the instruction pointer, the internal evaluation stack, and the state of any exception handling. When the execution is resumed by awaiting on the next object returned by the asynchronous generator’s methods, the function can proceed exactly as if the yield expression were just another external call. The value of the yield expression after resuming depends on the method which resumed the execution. If$_{__anext__()}$ is used then the result is$_{None}$ . Otherwise, if$_{asend()}$ is used, then the result will be the value passed in to that method. In an asynchronous generator function, yield expressions are allowed anywhere in a$_{try}$ construct. However, if an asynchronous generator is not resumed before it is finalized (by reaching a zero reference count or by being garbage collected), then a yield expression within a $_{try}$construct could result in a failure to execute pending$_{finally}$ clauses. In this case, it is the responsibility of the event loop or scheduler running the asynchronous generator to call the asynchronous generator-iterator’s$_{aclose()}$ method and run the resulting coroutine object, thus allowing any pending$_{finally}$ clauses to execute. 69 6.2. Atoms Asynchronous generator functions