Raymond's latest update. Note Status is still Draft until

confirmation of Accepted status comes from Guido.
This commit is contained in:
Barry Warsaw 2002-04-01 16:04:27 +00:00
parent ade4c48fa4
commit b17080888c
1 changed files with 65 additions and 119 deletions

View File

@ -12,7 +12,7 @@ Post-History:
Abstract
This PEP introduces three orthogonal (not mutually exclusive) ideas
This PEP introduces two orthogonal (not mutually exclusive) ideas
for enhancing the generators introduced in Python version 2.2 [1].
The goal is to increase the convenience, utility, and power
of generators.
@ -49,7 +49,7 @@ Rationale
The next steps in the evolution of generators are:
1. Add a new builtin function, indexed() which was made possible
1. Add a new builtin function, iterindexed() which was made possible
once iterators and generators became available. It provides
all iterables with the same advantage that iteritems() affords
to dictionaries -- a compact, readable, reliable index notation.
@ -58,19 +58,25 @@ Rationale
that provides a simple way to convert a list comprehension into
a generator whenever memory issues arise.
3. Add a generator method to enable exceptions to be passed to a
generator. Currently, there is no clean method for triggering
exceptions from outside the generator. Also, generator exception
passing helps mitigate the try/finally prohibition for generators.
All of the suggestions are designed to take advantage of the
existing implementation and require little additional effort to
incorporate. Each is backward compatible and requires no new
keywords. The three generator tools go into Python 2.3 when
keywords. The two generator tools go into Python 2.3 when
generators become final and are not imported from __future__.
BDFL Pronouncements
1. The new built-in function is ACCEPTED. There needs to be further
discussion on the best name for the function.
2. Generator comprehensions are REJECTED. The rationale is that
the benefits are marginal since generators can already be coded directly
and the costs are high because implementation and maintenance require
major efforts with the parser.
Reference Implementation
There is not currently a CPython implementation; however, a simulation
@ -91,15 +97,16 @@ Reference Implementation
Specification for a new builtin:
Specification for a new builtin [ACCEPTED PROPOSAL]:
def indexed(collection, start=0, stop=None):
'Generates an indexed series: (0,seqn[0]), (1,seqn[1]) ...'
gen = iter(collection)
cnt = start
while stop is None or cnt<stop:
yield (cnt, gen.next())
cnt += 1
def iterindexed(collection):
'Generates an indexed series: (0,seqn[0]), (1,seqn[1]) ...'
i = 0
it = iter(collection)
while 1:
yield (i, it.next())
i += 1
Note A: PEP 212 Loop Counter Iteration [2] discussed several
@ -115,7 +122,7 @@ Specification for a new builtin:
There are other PEPs which touch on related issues: integer iterators,
integer for-loops, and one for modifying the arguments to range and
xrange. The indexed() proposal does not preclude the other proposals
xrange. The iterindexed() proposal does not preclude the other proposals
and it still meets an important need even if those are adopted -- the need
to count items in any iterable. The other proposals give a means of
producing an index but not the corresponding value. This is especially
@ -129,19 +136,46 @@ Specification for a new builtin:
main argument for a builtin was that the function is destined to be
part of a core programming style, applicable to any object with an
iterable interface. Just as zip() solves the problem of looping
over multiple sequences, the indexed() function solves the loop
over multiple sequences, the iterindexed() function solves the loop
counter problem.
If only one builtin is allowed, then indexed() is the most important
If only one builtin is allowed, then iterindexed() is the most important
general purpose tool, solving the broadest class of problems while
improving program brevity, clarity and reliability.
Note C: Various alternative names have been proposed:
iterindexed()-- five syllables is a mouthfull
index() -- nice verb but could be confused the .index() method
indexed() -- widely liked however adjectives should be avoided
count() -- direct and explicit but often used in other contexts
itercount() -- direct, explicit and hated by more than one person
enumerate() -- a contender but doesn't mention iteration or indices
iteritems() -- conflicts with key:value concept for dictionaries
Note D: This function was originally proposed with optional start and
stop arguments. GvR pointed out that the function call
iterindexed(seqn,4,6) had an alternate, plausible interpretation as a
slice that would return the fourth and fifth elements of the sequence.
To avoid the ambiguity, the optional arguments were dropped eventhough
it meant losing flexibity as a loop counter. That flexiblity was most
important for the common case of counting from one, as in:
for linenum, line in iterindexed(source): print linenum, line
Comments from GvR: filter and map should die and be subsumed into list
comprehensions, not grow more variants. I'd rather introduce builtins
that do iterator algebra (e.g. the iterzip that I've often used as
an example).
I like the idea of having some way to iterate over a sequence and
its index set in parallel. It's fine for this to be a builtin.
I don't like the name "indexed"; adjectives do not make good
function names. Maybe iterindexed()?
Comments from Ka-Ping Yee: I'm also quite happy with everything you
proposed ... and the extra builtins (really 'indexed' in particular)
are things I have wanted for a long time.
@ -158,8 +192,8 @@ Specification for a new builtin:
obsolete. Adding a separate module for iterator utilities seems like
a good idea.
Comments from the Community: The response to the indexed() proposal has
been close to 100% favorable. Almost everyone loves the idea.
Comments from the Community: The response to the iterindexed() proposal
has been close to 100% favorable. Almost everyone loves the idea.
Author response: Prior to these comments, four builtins were proposed.
After the comments, xmap xfilter and xzip were withdrawn. The one
@ -173,7 +207,7 @@ Specification for a new builtin:
Specification for Generator Comprehensions:
Specification for Generator Comprehensions [REJECTED PROPOSAL]:
If a list comprehension starts with a 'yield' keyword, then
express the comprehension with a generator. For example:
@ -237,6 +271,15 @@ Specification for Generator Comprehensions:
language, where most algorithms use lazy infinite sequences, and I
just don't think that's where its future lies.
I don't think it's worth the trouble. I expect it will take a lot
of work to hack it into the code generator: it has to create a
separate code object in order to be a generator. List
comprehensions are inlined, so I expect that the generator
comprehension code generator can't share much with the list
comprehension code generator. And this for something that's not
that common and easily done by writing a 2-line helper function.
IOW the ROI isn't high enough.
Comments from Ka-Ping Yee: I am very happy with the things you have
proposed in this PEP. I feel quite positive about generator
comprehensions and have no reservations. So a +1 on that.
@ -277,103 +320,6 @@ Specification for Generator Comprehensions:
embedded yield.
Specification for Generator Exception Passing:
Add a .throw(exception) method to the generator interface:
def logger():
start = time.time()
log = []
try:
while 1:
log.append( time.time() - start )
yield log[-1]
except WriteLog:
return log
g = logger()
for i in [10,20,40,80,160]:
testsuite(i)
g.next()
g.throw(WriteLog)
There is no existing work-around for triggering an exception
inside a generator. This is a true deficiency. It is the only
case in Python where active code cannot be excepted to or through.
Generator exception passing also helps address an intrinsic limitation
on generators, the prohibition against their using try/finally to
trigger clean-up code [1]. Without .throw(), the current work-around
forces the resolution or clean-up code to be moved outside the generator.
Note A: The name of the throw method was selected for several
reasons. Raise is a keyword and so cannot be used as a method
name. Unlike raise which immediately raises an exception from the
current execution point, throw will first return to the generator
and then raise the exception. The word throw is suggestive of
putting the exception in another location. The word throw is
already associated with exceptions in other languages.
Alternative method names were considered: resolve(), signal(),
genraise(), raiseinto(), and flush(). None of these seem to fit
as well as throw().
Note B: The throw syntax should exactly match raise's syntax:
throw([expression, [expression, [expression]]])
Accordingly, it should be implemented to handle all of the following:
raise string g.throw(string)
raise string, data g.throw(string,data)
raise class, instance g.throw(class,instance)
raise instance g.throw(instance)
raise g.throw()
Comments from GvR: I'm not convinced that the cleanup problem that
this is trying to solve exists in practice. I've never felt the need
to put yield inside a try/except. I think the PEP doesn't make enough
of a case that this is useful.
Comments from Ka-Ping Yee: I agree that the exception issue needs to
be resolved and [that] you have suggested a fine solution.
Comments from Neil Schemenauer: The exception passing idea is one I
hadn't thought of before and looks interesting. If we enable the
passing of values back, then we should add this feature too.
Comments for Magnus Lie Hetland: Even though I cannot speak for the
ease of implementation, I vote +1 for the exception passing mechanism.
Comments from the Community: The response has been mostly favorable. One
negative comment from GvR is shown above. The other was from Martin von
Loewis who was concerned that it could be difficult to implement and
is withholding his support until a working patch is available. To probe
Martin's comment, I checked with the implementers of the original
generator PEP for an opinion on the ease of implementation. They felt that
implementation would be straight-forward and could be grafted onto the
existing implementation without disturbing its internals.
Author response: When the sole use of generators is to simplify writing
iterators for lazy producers, then the odds of needing generator
exception passing are slim. If, on the other hand, generators
are used to write lazy consumers, create coroutines, generate output
streams, or simply for their marvelous capability for restarting a
previously frozen state, THEN the need to raise exceptions will
come up frequently.
I'm no judge of what is truly Pythonic, but am still astonished
that there can exist blocks of code that can't be excepted to or
through, that the try/finally combination is blocked, and that the
only work-around is to rewrite as a class and move the exception
code out of the function or method being excepted.
References
[1] PEP 255 Simple Generators