273 lines
10 KiB
Plaintext
273 lines
10 KiB
Plaintext
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: Rejected
|
||
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.
|
||
|
||
Pronouncement
|
||
|
||
This PEP is rejected. There were a number of fixable issues with
|
||
the proposal (see the fixups listed in Raymond Hettinger's
|
||
python-dev post on 18 June 2005). However, even with the fixups the
|
||
proposal did not garner support. Specifically, Guido did not buy
|
||
the premise that the range() format needed fixing, "The whole point
|
||
(15 years ago) of range() was to *avoid* needing syntax to specify a
|
||
loop over numbers. I think it's worked out well and there's nothing
|
||
that needs to be fixed (except range() needs to become an iterator,
|
||
which it will in Python 3.0)."
|
||
|
||
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 or_expr 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:
|