PEP 284, Integer for-loops, Eppstein & Ewing
This commit is contained in:
parent
6d5f340df7
commit
9b27c83c79
|
@ -0,0 +1,261 @@
|
|||
PEP: 284
|
||||
Title: Integer for-loops
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: eppstein@ics.uci.edu (David Eppstein),
|
||||
greg@cosc.canterbury.ac.nz (Greg Ewing)
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Created: 1-Mar-2002
|
||||
Python-Version: 2.3
|
||||
Post-History:
|
||||
|
||||
|
||||
Abstract
|
||||
|
||||
This PEP proposes to simplify iteration over intervals of
|
||||
integers, by extending the range of expressions allowed after a
|
||||
"for" keyword to allow three-way comparisons such as
|
||||
|
||||
for lower <= var < upper:
|
||||
|
||||
in place of the current
|
||||
|
||||
for item in list:
|
||||
|
||||
syntax. The resulting loop or list iteration will loop over all
|
||||
values of var that make the comparison true, starting from the
|
||||
left endpoint of the given interval.
|
||||
|
||||
|
||||
Rationale
|
||||
|
||||
One of the most common uses of for-loops in Python is to iterate
|
||||
over an interval of integers. Python provides functions range()
|
||||
and xrange() to generate lists and iterators for such intervals,
|
||||
which work best for the most frequent case: half-open intervals
|
||||
increasing from zero. However, the range() syntax is more awkward
|
||||
for open or closed intervals, and lacks symmetry when reversing
|
||||
the order of iteration. In addition, the call to an unfamiliar
|
||||
function makes it difficult for newcomers to Python to understand
|
||||
code that uses range() or xrange().
|
||||
|
||||
The perceived lack of a natural, intuitive integer iteration
|
||||
syntax has led to heated debate on python-list, and spawned at
|
||||
least four PEPs before this one. PEP 204 [1] (rejected) proposed
|
||||
to re-use Python's slice syntax for integer ranges, leading to a
|
||||
terser syntax but not solving the readability problem of
|
||||
multi-argument range(). PEP 212 [2] (deferred) proposed several
|
||||
syntaxes for directly converting a list to a sequence of integer
|
||||
indices, in place of the current idiom
|
||||
|
||||
range(len(list))
|
||||
|
||||
for such conversion, and PEP 281 [3] proposes to simplify the same
|
||||
idiom by allowing it to be written as
|
||||
|
||||
range(list).
|
||||
|
||||
PEP 276 [4] proposes to allow automatic conversion of integers to
|
||||
iterators, simplifying the most common half-open case but not
|
||||
addressing the complexities of other types of interval.
|
||||
Additional alternatives have been discussed on python-list.
|
||||
|
||||
The solution described here is to allow a three-way comparison
|
||||
after a "for" keyword, both in the context of a for-loop and of a
|
||||
list comprehension:
|
||||
|
||||
for lower <= var < upper:
|
||||
|
||||
This would cause iteration over an interval of consecutive
|
||||
integers, beginning at the left bound in the comparison and ending
|
||||
at the right bound. The exact comparison operations used would
|
||||
determine whether the interval is open or closed at either end and
|
||||
whether the integers are considered in ascending or descending
|
||||
order.
|
||||
|
||||
This syntax closely matches standard mathematical notation, so is
|
||||
likely to be more familiar to Python novices than the current
|
||||
range() syntax. Open and closed interval endpoints are equally
|
||||
easy to express, and the reversal of an integer interval can be
|
||||
formed simply by swapping the two endpoints and reversing the
|
||||
comparisons. In addition, the semantics of such a loop would
|
||||
closely resemble one way of interpreting the existing Python
|
||||
for-loops:
|
||||
|
||||
for item in list
|
||||
|
||||
iterates over exactly those values of item that cause the
|
||||
expression
|
||||
|
||||
item in list
|
||||
|
||||
to be true. Similarly, the new format
|
||||
|
||||
for lower <= var < upper:
|
||||
|
||||
would iterate over exactly those integer values of var that cause
|
||||
the expression
|
||||
|
||||
lower <= var < upper
|
||||
|
||||
to be true.
|
||||
|
||||
|
||||
Specification
|
||||
|
||||
We propose to extend the syntax of a for statement, currently
|
||||
|
||||
for_stmt: "for" target_list "in" expression_list ":" suite
|
||||
["else" ":" suite]
|
||||
|
||||
as described below:
|
||||
|
||||
for_stmt: "for" for_test ":" suite ["else" ":" suite]
|
||||
for_test: target_list "in" expression_list |
|
||||
or_expr less_comp or_expr less_comp or_expr |
|
||||
or_expr greater_comp identifier greater_comp or_expr
|
||||
less_comp: "<" | "<="
|
||||
greater_comp: ">" | ">="
|
||||
|
||||
Similarly, we propose to extend the syntax of list comprehensions,
|
||||
currently
|
||||
|
||||
list_for: "for" expression_list "in" testlist [list_iter]
|
||||
|
||||
by replacing it with:
|
||||
|
||||
list_for: "for" for_test [list_iter]
|
||||
|
||||
In all cases the expression formed by for_test would be subject to
|
||||
the same precedence rules as comparisons in expressions. The two
|
||||
comp_operators in a for_test must be required to be both of
|
||||
similar types, unlike chained comparisons in expressions which do
|
||||
not have such a restriction.
|
||||
|
||||
We refer to the two or_expr's occurring on the left and right
|
||||
sides of the for-loop syntax as the bounds of the loop, and the
|
||||
middle or_expr as the variable of the loop. When a for-loop using
|
||||
the new syntax is executed, the expressions for both bounds will
|
||||
be evaluated, and an iterator object created that iterates through
|
||||
all integers between the two bounds according to the comparison
|
||||
operations used. The iterator will begin with an integer equal or
|
||||
near to the left bound, and then step through the remaining
|
||||
integers with a step size of +1 or -1 if the comparison operation
|
||||
is in the set described by less_comp or greater_comp respectively.
|
||||
The execution will then proceed as if the expression had been
|
||||
|
||||
for variable in iterator
|
||||
|
||||
where "variable" refers to the variable of the loop and "iterator"
|
||||
refers to the iterator created for the given integer interval.
|
||||
|
||||
The values taken by the loop variable in an integer for-loop may
|
||||
be either plain integers or long integers, according to the
|
||||
magnitude of the bounds. Both bounds of an integer for-loop must
|
||||
evaluate to a real numeric type (integer, long, or float). Any
|
||||
other value will cause the for-loop statement to raise a TypeError
|
||||
exception.
|
||||
|
||||
|
||||
Issues
|
||||
|
||||
The following issues were raised in discussion of this and related
|
||||
proposals on the Python list.
|
||||
|
||||
- Should the right bound be evaluated once, or every time through
|
||||
the loop? Clearly, it only makes sense to evaluate the left
|
||||
bound once. For reasons of consistency and efficiency, we have
|
||||
chosen the same convention for the right bound.
|
||||
|
||||
- Although the new syntax considerably simplifies integer
|
||||
for-loops, list comprehensions using the new syntax are not as
|
||||
simple. We feel that this is appropriate since for-loops are
|
||||
more frequent than comprehensions.
|
||||
|
||||
- The proposal does not allow access to integer iterator objects
|
||||
such as would be created by xrange. True, but we see this as a
|
||||
shortcoming in the general list-comprehension syntax, beyond the
|
||||
scope of this proposal. In addition, xrange() will still be
|
||||
available.
|
||||
|
||||
- The proposal does not allow increments other than 1 and -1.
|
||||
More general arithmetic progressions would need to be created by
|
||||
range() or xrange(), or by a list comprehension syntax such as
|
||||
|
||||
[2*x for 0 <= x <= 100]
|
||||
|
||||
- The position of the loop variable in the middle of a three-way
|
||||
comparison is not as apparent as the variable in the present
|
||||
|
||||
for item in list
|
||||
|
||||
syntax, leading to a possible loss of readability. We feel that
|
||||
this loss is outweighed by the increase in readability from a
|
||||
natural integer iteration syntax.
|
||||
|
||||
- To some extent, this PEP addresses the same issues as PEP 276
|
||||
[4]. We feel that the two PEPs are not in conflict since PEP
|
||||
276 is primarily concerned with half-open ranges starting in 0
|
||||
(the easy case of range()) while this PEP is primarily concerned
|
||||
with simplifying all other cases. However, if this PEP is
|
||||
approved, its new simpler syntax for integer loops could to some
|
||||
extent reduce the motivation for PEP 276.
|
||||
|
||||
- It is not clear whether it makes sense to allow floating point
|
||||
bounds for an integer loop: if a float represents an inexact
|
||||
value, how can it be used to determine an exact sequence of
|
||||
integers? On the other hand, disallowing float bounds would
|
||||
make it difficult to use floor() and ceiling() in integer
|
||||
for-loops, as it is difficult to use them now with range(). We
|
||||
have erred on the side of flexibility, but this may lead to some
|
||||
implementation difficulties in determining the smallest and
|
||||
largest integer values that would cause a given comparison to be
|
||||
true.
|
||||
|
||||
- Should types other than int, long, and float be allowed as
|
||||
bounds? Another choice would be to convert all bounds to
|
||||
integers by int(), and allow as bounds anything that can be so
|
||||
converted instead of just floats. However, this would change
|
||||
the semantics: 0.3 <= x is not the same as int(0.3) <= x, and it
|
||||
would be confusing for a loop with 0.3 as lower bound to start
|
||||
at zero. Also, in general int(f) can be very far from f.
|
||||
|
||||
|
||||
Implementation
|
||||
|
||||
An implementation is not available at this time. Implementation
|
||||
is not expected to pose any great difficulties: the new syntax
|
||||
could, if necessary, be recognized by parsing a general expression
|
||||
after each "for" keyword and testing whether the top level
|
||||
operation of the expression is "in" or a three-way comparison.
|
||||
The Python compiler would convert any instance of the new syntax
|
||||
into a loop over the items in a special iterator object.
|
||||
|
||||
|
||||
References
|
||||
|
||||
[1] PEP 204, Range Literals
|
||||
http://www.python.org/peps/pep-0204.html
|
||||
|
||||
[2] PEP 212, Loop Counter Iteration
|
||||
http://www.python.org/peps/pep-0212.html
|
||||
|
||||
[3] PEP 281, Loop Counter Iteration with range and xrange
|
||||
http://www.python.org/peps/pep-0281.html
|
||||
|
||||
[4] PEP 276, Simple Iterator for ints
|
||||
http://www.python.org/peps/pep-0276.html
|
||||
|
||||
|
||||
Copyright
|
||||
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
fill-column: 70
|
||||
End:
|
Loading…
Reference in New Issue