PEP: 284 Title: Integer for-loops Version: $Revision$ Last-Modified:
$Date$ Author: David Eppstein <eppstein@ics.uci.edu>, Greg Ewing
<greg.ewing@canterbury.ac.nz> Status: Rejected Type: Standards Track
Content-Type: text/x-rst 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 \[5\]\_). 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/dev/peps/pep-0204/

.. \[2\] PEP 212, Loop Counter Iteration
http://www.python.org/dev/peps/pep-0212/

.. \[3\] PEP 281, Loop Counter Iteration with range and xrange
http://www.python.org/dev/peps/pep-0281/

.. \[4\] PEP 276, Simple Iterator for ints
http://www.python.org/dev/peps/pep-0276/

.. \[5\] Raymond Hettinger, Propose updating PEP 284 -- Integer
for-loops
https://mail.python.org/pipermail/python-dev/2005-June/054316.html

Copyright
=========

This document has been placed in the public domain.

.. Local Variables: mode: indented-text indent-tabs-mode: nil
fill-column: 70 End:
