[Python-ideas] Pass a function as the argument "step" of range()
Terry Reedy
tjreedy at udel.edu
Thu Jul 2 21:53:40 CEST 2015
On 7/2/2015 2:30 AM, Pierre Quentel wrote:
> In languages such as Javascript, the incrementation of a for loop
> counter can be done by an operation, for instance :
>
> for(i=1; i<N; i*=2)
>
> would iterate on the powers of 2 lesser than N.
>
> To achieve the same thing in Python we currently can't use range()
> because it increments by an integer (the argument "step"). An option is
> to build a generator like :
>
> def gen(N):
> i = 1
> while i<=N:
> yield i
> i *= 2
>
> then we can iterate on gen(N).
>
> My proposal is that besides an integer, range() would accept a function
> as the "step" argument, taking the current counter as its argument and
> returning the new counter value. Here is a basic pure-Python
> implementation :
>
> import operator
>
> class Range:
>
> def __init__(self, start, stop, incrementor):
> self.start, self.stop = start, stop
> self.incrementor = incrementor
> # Function to compare current counter and stop value : <= or >=
> self.comp = operator.ge <http://operator.ge> if
> self.stop>self.start else operator.le
> self.counter = None
>
> def __iter__(self):
> return self
>
> def __next__(self):
> if self.counter is None:
> self.counter = self.start
> else:
> self.counter = self.incrementor(self.counter)
> if self.comp(self.counter, self.stop):
> raise StopIteration
> return self.counter
The idea of iterating by non-constant steps is valid. Others have given
multiple options for doing so.
The idea of stuffing this into range is not valid. It does not fit into
what 3.x range actually is. The Range class above is a one-use
iterator. This post and your counter-responses seem to miss what others
have alluded to. 3.x range class is something quite different -- a
reusable collections.abc.Sequence class, with a separate range_iterator
class.
The range class has the following sequence methods with efficient O(1)
implementations: __contains__, __getitem__, __iter__, __len__,
__reversed__, count, and index methods. Having such efficient methods
is part of the design goal for 3.x range. Your proposal would breaks
all of these except __iter__ (and make that slower too) in the sense
that the replacements would require the O(n) calculation of list(self),
whereas avoiding this is part of the purpose of range.
While some of these methods are rarely used, __reversed__ is certainly
not rare. People depend on the fact that the often easy to write
reversed(up-range(...)) is equivalent in output *and speed* to the often
harder to write iter(down-range(...).
A trivial case is counting down from n to 0
for i in reversed(range(n+1):
versus
for i in range(n, -1, -1):
People do not always get the latter correct.
Now onsider a more general case, such as
r = range(11, 44000000000, 1335)
r1 = reversed(r)
versus the equivalent
r2 = iter(range(43999999346, 10, -1335))
43999999346 is r[-1], which uses __getitem__.
Using this is much easier than figuring out the following (which
__reversed__ has built in).
def reversed_start(start, stop, step):
rem = (stop - start) % step
return stop - (rem if rem else step)
--
Terry Jan Reedy
More information about the Python-ideas
mailing list