diff --git a/pep-0533.txt b/pep-0533.txt
new file mode 100644
index 000000000..9d096d9dd
--- /dev/null
+++ b/pep-0533.txt
@@ -0,0 +1,798 @@
+PEP: 533
+Title: Deterministic cleanup for iterators
+Version: $Revision$
+Last-Modified: $Date$
+Author: Nathaniel J. Smith
+Status: Draft
+Type: Standards Track
+Content-Type: text/x-rst
+Created: 18-Oct-2016
+Post-History: 18-Oct-2016
+
+Abstract
+========
+
+We propose to extend the iterator protocol with a new
+``__(a)iterclose__`` slot, which is called automatically on exit from
+``(async) for`` loops, regardless of how they exit. This allows for
+convenient, deterministic cleanup of resources held by iterators
+without reliance on the garbage collector. This is especially valuable
+for asynchronous generators.
+
+
+Note on timing
+==============
+
+In practical terms, the proposal here is divided into two separate
+parts: the handling of async iterators, which should ideally be
+implemented ASAP, and the handling of regular iterators, which is a
+larger but more relaxed project that can't start until 3.7 at the
+earliest. But since the changes are closely related, and we probably
+don't want to end up with async iterators and regular iterators
+diverging in the long run, it seems useful to look at them together.
+
+
+Background and motivation
+=========================
+
+Python iterables often hold resources which require cleanup. For
+example: ``file`` objects need to be closed; the `WSGI spec
+`_ adds a ``close`` method
+on top of the regular iterator protocol and demands that consumers
+call it at the appropriate time (though forgetting to do so is a
+`frequent source of bugs
+`_);
+and PEP 342 (based on PEP 325) extended generator objects to add a
+``close`` method to allow generators to clean up after themselves.
+
+Generally, objects that need to clean up after themselves also define
+a ``__del__`` method to ensure that this cleanup will happen
+eventually, when the object is garbage collected. However, relying on
+the garbage collector for cleanup like this causes serious problems in
+several cases:
+
+- In Python implementations that do not use reference counting
+ (e.g. PyPy, Jython), calls to ``__del__`` may be arbitrarily delayed
+ -- yet many situations require *prompt* cleanup of
+ resources. Delayed cleanup produces problems like crashes due to
+ file descriptor exhaustion, or WSGI timing middleware that collects
+ bogus times.
+
+- Async generators (PEP 525) can only perform cleanup under the
+ supervision of the appropriate coroutine runner. ``__del__`` doesn't
+ have access to the coroutine runner; indeed, the coroutine runner
+ might be garbage collected before the generator object. So relying
+ on the garbage collector is effectively impossible without some kind
+ of language extension. (PEP 525 does provide such an extension, but
+ it has a number of limitations that this proposal fixes; see the
+ "alternatives" section below for discussion.)
+
+.. XX add discussion of:
+
+ - Causality preservation, context preservation
+
+ - Exception swallowing
+
+Fortunately, Python provides a standard tool for doing resource
+cleanup in a more structured way: ``with`` blocks. For example, this
+code opens a file but relies on the garbage collector to close it::
+
+ def read_newline_separated_json(path):
+ for line in open(path):
+ yield json.loads(line)
+
+ for document in read_newline_separated_json(path):
+ ...
+
+and recent versions of CPython will point this out by issuing a
+``ResourceWarning``, nudging us to fix it by adding a ``with`` block::
+
+ def read_newline_separated_json(path):
+ with open(path) as file_handle: # <-- with block
+ for line in file_handle:
+ yield json.loads(line)
+
+ for document in read_newline_separated_json(path): # <-- outer for loop
+ ...
+
+But there's a subtlety here, caused by the interaction of ``with``
+blocks and generators. ``with`` blocks are Python's main tool for
+managing cleanup, and they're a powerful one, because they pin the
+lifetime of a resource to the lifetime of a stack frame. But this
+assumes that someone will take care of cleaning up the stack
+frame... and for generators, this requires that someone ``close``
+them.
+
+In this case, adding the ``with`` block *is* enough to shut up the
+``ResourceWarning``, but this is misleading -- the file object cleanup
+here is still dependent on the garbage collector. The ``with`` block
+will only be unwound when the ``read_newline_separated_json``
+generator is closed. If the outer ``for`` loop runs to completion then
+the cleanup will happen immediately; but if this loop is terminated
+early by a ``break`` or an exception, then the ``with`` block won't
+fire until the generator object is garbage collected.
+
+The correct solution requires that all *users* of this API wrap every
+``for`` loop in its own ``with`` block::
+
+ with closing(read_newline_separated_json(path)) as genobj:
+ for document in genobj:
+ ...
+
+This gets even worse if we consider the idiom of decomposing a complex
+pipeline into multiple nested generators::
+
+ def read_users(path):
+ with closing(read_newline_separated_json(path)) as gen:
+ for document in gen:
+ yield User.from_json(document)
+
+ def users_in_group(path, group):
+ with closing(read_users(path)) as gen:
+ for user in gen:
+ if user.group == group:
+ yield user
+
+In general if you have N nested generators then you need N+1 ``with``
+blocks to clean up 1 file. And good defensive programming would
+suggest that any time we use a generator, we should assume the
+possibility that there could be at least one ``with`` block somewhere
+in its (potentially transitive) call stack, either now or in the
+future, and thus always wrap it in a ``with``. But in practice,
+basically nobody does this, because programmers would rather write
+buggy code than tiresome repetitive code. In simple cases like this
+there are some workarounds that good Python developers know (e.g. in
+this simple case it would be idiomatic to pass in a file handle
+instead of a path and move the resource management to the top level),
+but in general we cannot avoid the use of ``with``/``finally`` inside
+of generators, and thus dealing with this problem one way or
+another. When beauty and correctness fight then beauty tends to win,
+so it's important to make correct code beautiful.
+
+Still, is this worth fixing? Until async generators came along I would
+have argued yes, but that it was a low priority, since everyone seems
+to be muddling along okay -- but async generators make it much more
+urgent. Async generators cannot do cleanup *at all* without some
+mechanism for deterministic cleanup that people will actually use, and
+async generators are particularly likely to hold resources like file
+descriptors. (After all, if they weren't doing I/O, they'd be
+generators, not async generators.) So we have to do something, and it
+might as well be a comprehensive fix to the underlying problem. And
+it's much easier to fix this now when async generators are first
+rolling out, then it will be to fix it later.
+
+The proposal itself is simple in concept: add a ``__(a)iterclose__``
+method to the iterator protocol, and have (async) ``for`` loops call
+it when the loop is exited, even if this occurs via ``break`` or
+exception unwinding. Effectively, we're taking the current cumbersome
+idiom (``with`` block + ``for`` loop) and merging them together into a
+fancier ``for``. This may seem non-orthogonal, but makes sense when
+you consider that the existence of generators means that ``with``
+blocks actually depend on iterator cleanup to work reliably, plus
+experience showing that iterator cleanup is often a desireable feature
+in its own right.
+
+
+Alternatives
+============
+
+PEP 525 asyncgen hooks
+----------------------
+
+PEP 525 proposes a `set of global thread-local hooks managed by new
+``sys.{get/set}_asyncgen_hooks()`` functions
+`_, which
+allow event loops to integrate with the garbage collector to run
+cleanup for async generators. In principle, this proposal and PEP 525
+are complementary, in the same way that ``with`` blocks and
+``__del__`` are complementary: this proposal takes care of ensuring
+deterministic cleanup in most cases, while PEP 525's GC hooks clean up
+anything that gets missed. But ``__aiterclose__`` provides a number of
+advantages over GC hooks alone:
+
+- The GC hook semantics aren't part of the abstract async iterator
+ protocol, but are instead restricted `specifically to the async
+ generator concrete type
+ `_. If
+ you have an async iterator implemented using a class, like::
+
+ class MyAsyncIterator:
+ async def __anext__():
+ ...
+
+ then you can't refactor this into an async generator without
+ changing its semantics, and vice-versa. This seems very
+ unpythonic. (It also leaves open the question of what exactly
+ class-based async iterators are supposed to do, given that they face
+ exactly the same cleanup problems as async generators.)
+ ``__aiterclose__``, on the other hand, is defined at the protocol
+ level, so it's duck-type friendly and works for all iterators, not
+ just generators.
+
+- Code that wants to work on non-CPython implementations like PyPy
+ cannot in general rely on GC for cleanup. Without
+ ``__aiterclose__``, it's more or less guaranteed that developers who
+ develop and test on CPython will produce libraries that leak
+ resources when used on PyPy. Developers who do want to target
+ alternative implementations will either have to take the defensive
+ approach of wrapping every ``for`` loop in a ``with`` block, or else
+ carefully audit their code to figure out which generators might
+ possibly contain cleanup code and add ``with`` blocks around those
+ only. With ``__aiterclose__``, writing portable code becomes easy
+ and natural.
+
+- An important part of building robust software is making sure that
+ exceptions always propagate correctly without being lost. One of the
+ most exciting things about async/await compared to traditional
+ callback-based systems is that instead of requiring manual chaining,
+ the runtime can now do the heavy lifting of propagating errors,
+ making it *much* easier to write robust code. But, this beautiful
+ new picture has one major gap: if we rely on the GC for generator
+ cleanup, then exceptions raised during cleanup are lost. So, again,
+ with ``__aiterclose__``, developers who care about this kind of
+ robustness will either have to take the defensive approach of
+ wrapping every ``for`` loop in a ``with`` block, or else carefully
+ audit their code to figure out which generators might possibly
+ contain cleanup code. ``__aiterclose__`` plugs this hole by
+ performing cleanup in the caller's context, so writing more robust
+ code becomes the path of least resistance.
+
+- The WSGI experience suggests that there exist important
+ iterator-based APIs that need prompt cleanup and cannot rely on the
+ GC, even in CPython. For example, consider a hypothetical WSGI-like
+ API based around async/await and async iterators, where a response
+ handler is an async generator that takes request headers + an async
+ iterator over the request body, and yields response headers + the
+ response body. (This is actually the use case that got me interested
+ in async generators in the first place, i.e. this isn't
+ hypothetical.) If we follow WSGI in requiring that child iterators
+ must be closed properly, then without ``__aiterclose__`` the
+ absolute most minimalistic middleware in our system looks something
+ like::
+
+ async def noop_middleware(handler, request_header, request_body):
+ async with aclosing(handler(request_body, request_body)) as aiter:
+ async for response_item in aiter:
+ yield response_item
+
+ Arguably in regular code one can get away with skipping the ``with``
+ block around ``for`` loops, depending on how confident one is that
+ one understands the internal implementation of the generator. But
+ here we have to cope with arbitrary response handlers, so without
+ ``__aiterclose__``, this ``with`` construction is a mandatory part
+ of every middleware.
+
+ ``__aiterclose__`` allows us to eliminate the mandatory boilerplate
+ and an extra level of indentation from every middleware::
+
+ async def noop_middleware(handler, request_header, request_body):
+ async for response_item in handler(request_header, request_body):
+ yield response_item
+
+So the ``__aiterclose__`` approach provides substantial advantages
+over GC hooks.
+
+This leaves open the question of whether we want a combination of GC
+hooks + ``__aiterclose__``, or just ``__aiterclose__`` alone. Since
+the vast majority of generators are iterated over using a ``for`` loop
+or equivalent, ``__aiterclose__`` handles most situations before the
+GC has a chance to get involved. The case where GC hooks provide
+additional value is in code that does manual iteration, e.g.::
+
+ agen = fetch_newline_separated_json_from_url(...)
+ while True:
+ document = await type(agen).__anext__(agen)
+ if document["id"] == needle:
+ break
+ # doesn't do 'await agen.aclose()'
+
+If we go with the GC-hooks + ``__aiterclose__`` approach, this
+generator will eventually be cleaned up by GC calling the generator
+``__del__`` method, which then will use the hooks to call back into
+the event loop to run the cleanup code.
+
+If we go with the no-GC-hooks approach, this generator will eventually
+be garbage collected, with the following effects:
+
+- its ``__del__`` method will issue a warning that the generator was
+ not closed (similar to the existing "coroutine never awaited"
+ warning).
+
+- The underlying resources involved will still be cleaned up, because
+ the generator frame will still be garbage collected, causing it to
+ drop references to any file handles or sockets it holds, and then
+ those objects's ``__del__`` methods will release the actual
+ operating system resources.
+
+- But, any cleanup code inside the generator itself (e.g. logging,
+ buffer flushing) will not get a chance to run.
+
+The solution here -- as the warning would indicate -- is to fix the
+code so that it calls ``__aiterclose__``, e.g. by using a ``with``
+block::
+
+ async with aclosing(fetch_newline_separated_json_from_url(...)) as agen:
+ while True:
+ document = await type(agen).__anext__(agen)
+ if document["id"] == needle:
+ break
+
+Basically in this approach, the rule would be that if you want to
+manually implement the iterator protocol, then it's your
+responsibility to implement all of it, and that now includes
+``__(a)iterclose__``.
+
+GC hooks add non-trivial complexity in the form of (a) new global
+interpreter state, (b) a somewhat complicated control flow (e.g.,
+async generator GC always involves resurrection, so the details of PEP
+442 are important), and (c) a new public API in asyncio (``await
+loop.shutdown_asyncgens()``) that users have to remember to call at
+the appropriate time. (This last point in particular somewhat
+undermines the argument that GC hooks provide a safe backup to
+guarantee cleanup, since if ``shutdown_asyncgens()`` isn't called
+correctly then I *think* it's possible for generators to be silently
+discarded without their cleanup code being called; compare this to the
+``__aiterclose__``-only approach where in the worst case we still at
+least get a warning printed. This might be fixable.) All this
+considered, GC hooks arguably aren't worth it, given that the only
+people they help are those who want to manually call ``__anext__`` yet
+don't want to manually call ``__aiterclose__``. But Yury disagrees
+with me on this :-). And both options are viable.
+
+
+Always inject resources, and do all cleanup at the top level
+------------------------------------------------------------
+
+Several commentators on python-dev and python-ideas have suggested
+that a pattern to avoid these problems is to always pass resources in
+from above, e.g. ``read_newline_separated_json`` should take a file
+object rather than a path, with cleanup handled at the top level::
+
+ def read_newline_separated_json(file_handle):
+ for line in file_handle:
+ yield json.loads(line)
+
+ def read_users(file_handle):
+ for document in read_newline_separated_json(file_handle):
+ yield User.from_json(document)
+
+ with open(path) as file_handle:
+ for user in read_users(file_handle):
+ ...
+
+This works well in simple cases; here it lets us avoid the "N+1
+``with`` blocks problem". But unfortunately, it breaks down quickly
+when things get more complex. Consider if instead of reading from a
+file, our generator was reading from a streaming HTTP GET request --
+while handling redirects and authentication via OAUTH. Then we'd
+really want the sockets to be managed down inside our HTTP client
+library, not at the top level. Plus there are other cases where
+``finally`` blocks embedded inside generators are important in their
+own right: db transaction management, emitting logging information
+during cleanup (one of the major motivating use cases for WSGI
+``close``), and so forth. So this is really a workaround for simple
+cases, not a general solution.
+
+
+More complex variants of __(a)iterclose__
+-----------------------------------------
+
+The semantics of ``__(a)iterclose__`` are somewhat inspired by
+``with`` blocks, but context managers are more powerful:
+``__(a)exit__`` can distinguish between a normal exit versus exception
+unwinding, and in the case of an exception it can examine the
+exception details and optionally suppress
+propagation. ``__(a)iterclose__`` as proposed here does not have these
+powers, but one can imagine an alternative design where it did.
+
+However, this seems like unwarranted complexity: experience suggests
+that it's common for iterables to have ``close`` methods, and even to
+have ``__exit__`` methods that call ``self.close()``, but I'm not
+aware of any common cases that make use of ``__exit__``'s full
+power. I also can't think of any examples where this would be
+useful. And it seems unnecessarily confusing to allow iterators to
+affect flow control by swallowing exceptions -- if you're in a
+situation where you really want that, then you should probably use a
+real ``with`` block anyway.
+
+
+Specification
+=============
+
+This section describes where we want to eventually end up, though
+there are some backwards compatibility issues that mean we can't jump
+directly here. A later section describes the transition plan.
+
+
+Guiding principles
+------------------
+
+Generally, ``__(a)iterclose__`` implementations should:
+
+- be idempotent,
+- perform any cleanup that is appropriate on the assumption that the
+ iterator will not be used again after ``__(a)iterclose__`` is
+ called. In particular, once ``__(a)iterclose__`` has been called
+ then calling ``__(a)next__`` produces undefined behavior.
+
+And generally, any code which starts iterating through an iterable
+with the intention of exhausting it, should arrange to make sure that
+``__(a)iterclose__`` is eventually called, whether or not the iterator
+is actually exhausted.
+
+
+Changes to iteration
+--------------------
+
+The core proposal is the change in behavior of ``for`` loops. Given
+this Python code::
+
+ for VAR in ITERABLE:
+ LOOP-BODY
+ else:
+ ELSE-BODY
+
+we desugar to the equivalent of::
+
+ _iter = iter(ITERABLE)
+ _iterclose = getattr(type(_iter), "__iterclose__", lambda: None)
+ try:
+ traditional-for VAR in _iter:
+ LOOP-BODY
+ else:
+ ELSE-BODY
+ finally:
+ _iterclose(_iter)
+
+where the "traditional-for statement" here is meant as a shorthand for
+the classic 3.5-and-earlier ``for`` loop semantics.
+
+Besides the top-level ``for`` statement, Python also contains several
+other places where iterators are consumed. For consistency, these
+should call ``__iterclose__`` as well using semantics equivalent to
+the above. This includes:
+
+- ``for`` loops inside comprehensions
+- ``*`` unpacking
+- functions which accept and fully consume iterables, like
+ ``list(it)``, ``tuple(it)``, ``itertools.product(it1, it2, ...)``,
+ and others.
+
+In addition, a ``yield from`` that successfully exhausts the called
+generator should as a last step call its ``__iterclose__``
+method. (Rationale: ``yield from`` already links the lifetime of the
+calling generator to the called generator; if the calling generator is
+closed when half-way through a ``yield from``, then this will already
+automatically close the called generator.)
+
+
+Changes to async iteration
+--------------------------
+
+We also make the analogous changes to async iteration constructs,
+except that the new slot is called ``__aiterclose__``, and it's an
+async method that gets ``await``\ed.
+
+
+Modifications to basic iterator types
+-------------------------------------
+
+Generator objects (including those created by generator
+comprehensions):
+
+- ``__iterclose__`` calls ``self.close()``
+
+- ``__del__`` calls ``self.close()`` (same as now), and additionally
+ issues a ``ResourceWarning`` if the generator wasn't exhausted. This
+ warning is hidden by default, but can be enabled for those who want
+ to make sure they aren't inadverdantly relying on CPython-specific
+ GC semantics.
+
+Async generator objects (including those created by async generator
+comprehensions):
+
+- ``__aiterclose__`` calls ``self.aclose()``
+
+- ``__del__`` issues a ``RuntimeWarning`` if ``aclose`` has not been
+ called, since this probably indicates a latent bug, similar to the
+ "coroutine never awaited" warning.
+
+QUESTION: should file objects implement ``__iterclose__`` to close the
+file? On the one hand this would make this change more disruptive; on
+the other hand people really like writing ``for line in open(...):
+...``, and if we get used to iterators taking care of their own
+cleanup then it might become very weird if files don't.
+
+
+New convenience functions
+-------------------------
+
+The ``operator`` module gains two new functions, with semantics
+equivalent to the following::
+
+ def iterclose(it):
+ if not isinstance(it, collections.abc.Iterator):
+ raise TypeError("not an iterator")
+ if hasattr(type(it), "__iterclose__"):
+ type(it).__iterclose__(it)
+
+ async def aiterclose(ait):
+ if not isinstance(it, collections.abc.AsyncIterator):
+ raise TypeError("not an iterator")
+ if hasattr(type(ait), "__aiterclose__"):
+ await type(ait).__aiterclose__(ait)
+
+The ``itertools`` module gains a new iterator wrapper that can be used
+to selectively disable the new ``__iterclose__`` behavior::
+
+ # QUESTION: I feel like there might be a better name for this one?
+ class preserve(iterable):
+ def __init__(self, iterable):
+ self._it = iter(iterable)
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return next(self._it)
+
+ def __iterclose__(self):
+ # Swallow __iterclose__ without passing it on
+ pass
+
+Example usage (assuming that file objects implements
+``__iterclose__``)::
+
+ with open(...) as handle:
+ # Iterate through the same file twice:
+ for line in itertools.preserve(handle):
+ ...
+ handle.seek(0)
+ for line in itertools.preserve(handle):
+ ...
+
+::
+
+ @contextlib.contextmanager
+ def iterclosing(iterable):
+ it = iter(iterable)
+ try:
+ yield preserve(it)
+ finally:
+ iterclose(it)
+
+
+__iterclose__ implementations for iterator wrappers
+---------------------------------------------------
+
+Python ships a number of iterator types that act as wrappers around
+other iterators: ``map``, ``zip``, ``itertools.accumulate``,
+``csv.reader``, and others. These iterators should define a
+``__iterclose__`` method which calls ``__iterclose__`` in turn on
+their underlying iterators. For example, ``map`` could be implemented
+as::
+
+ # Helper function
+ map_chaining_exceptions(fn, items, last_exc=None):
+ for item in items:
+ try:
+ fn(item)
+ except BaseException as new_exc:
+ if new_exc.__context__ is None:
+ new_exc.__context__ = last_exc
+ last_exc = new_exc
+ if last_exc is not None:
+ raise last_exc
+
+ class map:
+ def __init__(self, fn, *iterables):
+ self._fn = fn
+ self._iters = [iter(iterable) for iterable in iterables]
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return self._fn(*[next(it) for it in self._iters])
+
+ def __iterclose__(self):
+ map_chaining_exceptions(operator.iterclose, self._iters)
+
+ def chain(*iterables):
+ try:
+ while iterables:
+ for element in iterables.pop(0):
+ yield element
+ except BaseException as e:
+ def iterclose_iterable(iterable):
+ operations.iterclose(iter(iterable))
+ map_chaining_exceptions(iterclose_iterable, iterables, last_exc=e)
+
+In some cases this requires some subtlety; for example,
+```itertools.tee``
+`_
+should not call ``__iterclose__`` on the underlying iterator until it
+has been called on *all* of the clone iterators.
+
+
+Example / Rationale
+-------------------
+
+The payoff for all this is that we can now write straightforward code
+like::
+
+ def read_newline_separated_json(path):
+ for line in open(path):
+ yield json.loads(line)
+
+and be confident that the file will receive deterministic cleanup
+*without the end-user having to take any special effort*, even in
+complex cases. For example, consider this silly pipeline::
+
+ list(map(lambda key: key.upper(),
+ doc["key"] for doc in read_newline_separated_json(path)))
+
+If our file contains a document where ``doc["key"]`` turns out to be
+an integer, then the following sequence of events will happen:
+
+1. ``key.upper()`` raises an ``AttributeError``, which propagates out
+ of the ``map`` and triggers the implicit ``finally`` block inside
+ ``list``.
+2. The ``finally`` block in ``list`` calls ``__iterclose__()`` on the
+ map object.
+3. ``map.__iterclose__()`` calls ``__iterclose__()`` on the generator
+ comprehension object.
+4. This injects a ``GeneratorExit`` exception into the generator
+ comprehension body, which is currently suspended inside the
+ comprehension's ``for`` loop body.
+5. The exception propagates out of the ``for`` loop, triggering the
+ ``for`` loop's implicit ``finally`` block, which calls
+ ``__iterclose__`` on the generator object representing the call to
+ ``read_newline_separated_json``.
+6. This injects an inner ``GeneratorExit`` exception into the body of
+ ``read_newline_separated_json``, currently suspended at the
+ ``yield``.
+7. The inner ``GeneratorExit`` propagates out of the ``for`` loop,
+ triggering the ``for`` loop's implicit ``finally`` block, which
+ calls ``__iterclose__()`` on the file object.
+8. The file object is closed.
+9. The inner ``GeneratorExit`` resumes propagating, hits the boundary
+ of the generator function, and causes
+ ``read_newline_separated_json``'s ``__iterclose__()`` method to
+ return successfully.
+10. Control returns to the generator comprehension body, and the outer
+ ``GeneratorExit`` continues propagating, allowing the
+ comprehension's ``__iterclose__()`` to return successfully.
+11. The rest of the ``__iterclose__()`` calls unwind without incident,
+ back into the body of ``list``.
+12. The original ``AttributeError`` resumes propagating.
+
+(The details above assume that we implement ``file.__iterclose__``; if
+not then add a ``with`` block to ``read_newline_separated_json`` and
+essentially the same logic goes through.)
+
+Of course, from the user's point of view, this can be simplified down
+to just:
+
+1. ``int.upper()`` raises an ``AttributeError``
+1. The file object is closed.
+2. The ``AttributeError`` propagates out of ``list``
+
+So we've accomplished our goal of making this "just work" without the
+user having to think about it.
+
+
+Transition plan
+===============
+
+While the majority of existing ``for`` loops will continue to produce
+identical results, the proposed changes will produce
+backwards-incompatible behavior in some cases. Example::
+
+ def read_csv_with_header(lines_iterable):
+ lines_iterator = iter(lines_iterable)
+ for line in lines_iterator:
+ column_names = line.strip().split("\t")
+ break
+ for line in lines_iterator:
+ values = line.strip().split("\t")
+ record = dict(zip(column_names, values))
+ yield record
+
+This code used to be correct, but after this proposal is implemented
+will require an ``itertools.preserve`` call added to the first ``for``
+loop.
+
+[QUESTION: currently, if you close a generator and then try to iterate
+over it then it just raises ``Stop(Async)Iteration``, so code the
+passes the same generator object to multiple ``for`` loops but forgets
+to use ``itertools.preserve`` won't see an obvious error -- the second
+``for`` loop will just exit immediately. Perhaps it would be better if
+iterating a closed generator raised a ``RuntimeError``? Note that
+files don't have this problem -- attempting to iterate a closed file
+object already raises ``ValueError``.]
+
+Specifically, the incompatibility happens when all of these factors
+come together:
+
+- The automatic calling of ``__(a)iterclose__`` is enabled
+- The iterable did not previously define ``__(a)iterclose__``
+- The iterable does now define ``__(a)iterclose__``
+- The iterable is re-used after the ``for`` loop exits
+
+So the problem is how to manage this transition, and those are the
+levers we have to work with.
+
+First, observe that the only async iterables where we propose to add
+``__aiterclose__`` are async generators, and there is currently no
+existing code using async generators (though this will start changing
+very soon), so the async changes do not produce any backwards
+incompatibilities. (There is existing code using async iterators, but
+using the new async for loop on an old async iterator is harmless,
+because old async iterators don't have ``__aiterclose__``.) In
+addition, PEP 525 was accepted on a provisional basis, and async
+generators are by far the biggest beneficiary of this PEP's proposed
+changes. Therefore, I think we should strongly consider enabling
+``__aiterclose__`` for ``async for`` loops and async generators ASAP,
+ideally for 3.6.0 or 3.6.1.
+
+For the non-async world, things are harder, but here's a potential
+transition path:
+
+In 3.7:
+
+Our goal is that existing unsafe code will start emitting warnings,
+while those who want to opt-in to the future can do that immediately:
+
+- We immediately add all the ``__iterclose__`` methods described
+ above.
+- If ``from __future__ import iterclose`` is in effect, then ``for``
+ loops and ``*`` unpacking call ``__iterclose__`` as specified above.
+- If the future is *not* enabled, then ``for`` loops and ``*``
+ unpacking do *not* call ``__iterclose__``. But they do call some
+ other method instead, e.g. ``__iterclose_warning__``.
+- Similarly, functions like ``list`` use stack introspection (!!) to
+ check whether their direct caller has ``__future__.iterclose``
+ enabled, and use this to decide whether to call ``__iterclose__`` or
+ ``__iterclose_warning__``.
+- For all the wrapper iterators, we also add ``__iterclose_warning__``
+ methods that forward to the ``__iterclose_warning__`` method of the
+ underlying iterator or iterators.
+- For generators (and files, if we decide to do that),
+ ``__iterclose_warning__`` is defined to set an internal flag, and
+ other methods on the object are modified to check for this flag. If
+ they find the flag set, they issue a ``PendingDeprecationWarning``
+ to inform the user that in the future this sequence would have led
+ to a use-after-close situation and the user should use
+ ``preserve()``.
+
+In 3.8:
+
+- Switch from ``PendingDeprecationWarning`` to ``DeprecationWarning``
+
+In 3.9:
+
+- Enable the ``__future__`` unconditionally and remove all the
+ ``__iterclose_warning__`` stuff.
+
+I believe that this satisfies the normal requirements for this kind of
+transition -- opt-in initially, with warnings targeted precisely to
+the cases that will be effected, and a long deprecation cycle.
+
+Probably the most controversial / risky part of this is the use of
+stack introspection to make the iterable-consuming functions sensitive
+to a ``__future__`` setting, though I haven't thought of any situation
+where it would actually go wrong yet...
+
+
+Acknowledgements
+================
+
+Thanks to Yury Selivanov, Armin Rigo, and Carl Friedrich Bolz for
+helpful discussion on earlier versions of this idea.
+
+
+Copyright
+=========
+
+This document has been placed in the public domain.